Merge cffda38cb3c3024034c5cfef5422bba4e603658c into 3f3b0175e8c66fb49b9a6d5a0cd1f8436d4c3ab6
This commit is contained in:
commit
9af1c21c59
@ -6,18 +6,25 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
||||
repoToken: 'none',
|
||||
staleIssueMessage: 'This issue is stale',
|
||||
stalePrMessage: 'This PR is stale',
|
||||
rottenIssueMessage: 'This issue is rotten',
|
||||
rottenPrMessage: 'This PR is rotten',
|
||||
closeIssueMessage: 'This issue is being closed',
|
||||
closePrMessage: 'This PR is being closed',
|
||||
daysBeforeStale: 1,
|
||||
daysBeforeRotten: -1,
|
||||
daysBeforeIssueStale: NaN,
|
||||
daysBeforePrStale: NaN,
|
||||
daysBeforeIssueRotten: NaN,
|
||||
daysBeforePrRotten: NaN,
|
||||
daysBeforeClose: 30,
|
||||
daysBeforeIssueClose: NaN,
|
||||
daysBeforePrClose: NaN,
|
||||
staleIssueLabel: 'Stale',
|
||||
rottenIssueLabel: 'Rotten',
|
||||
closeIssueLabel: '',
|
||||
exemptIssueLabels: '',
|
||||
stalePrLabel: 'Stale',
|
||||
rottenPrLabel: 'Rotten',
|
||||
closePrLabel: '',
|
||||
exemptPrLabels: '',
|
||||
onlyLabels: '',
|
||||
@ -31,6 +38,9 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
||||
removeStaleWhenUpdated: false,
|
||||
removeIssueStaleWhenUpdated: undefined,
|
||||
removePrStaleWhenUpdated: undefined,
|
||||
removeRottenWhenUpdated: false,
|
||||
removeIssueRottenWhenUpdated: undefined,
|
||||
removePrRottenWhenUpdated: undefined,
|
||||
ascending: false,
|
||||
deleteBranch: false,
|
||||
startDate: '',
|
||||
@ -50,6 +60,9 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
||||
labelsToRemoveWhenStale: '',
|
||||
labelsToRemoveWhenUnstale: '',
|
||||
labelsToAddWhenUnstale: '',
|
||||
labelsToRemoveWhenRotten: '',
|
||||
labelsToRemoveWhenUnrotten: '',
|
||||
labelsToAddWhenUnrotten: '',
|
||||
ignoreUpdates: false,
|
||||
ignoreIssueUpdates: undefined,
|
||||
ignorePrUpdates: undefined,
|
||||
|
||||
@ -159,11 +159,12 @@ test('processing an issue with no label and a start date as ECMAScript epoch in
|
||||
});
|
||||
|
||||
test('processing an issue with no label and a start date as ISO 8601 being before the issue creation date will make it stale and close it when it is old enough and days-before-close is set to 0', async () => {
|
||||
expect.assertions(2);
|
||||
expect.assertions(3);
|
||||
const january2000 = '2000-01-01T00:00:00Z';
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 0,
|
||||
daysBeforeRotten: 0,
|
||||
startDate: january2000.toString()
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -187,6 +188,7 @@ test('processing an issue with no label and a start date as ISO 8601 being befor
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toStrictEqual(1);
|
||||
expect(processor.rottenIssues.length).toStrictEqual(1);
|
||||
expect(processor.closedIssues.length).toStrictEqual(1);
|
||||
});
|
||||
|
||||
@ -222,6 +224,39 @@ test('processing an issue with no label and a start date as ISO 8601 being after
|
||||
expect(processor.closedIssues.length).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('processing an issue with no label and a start date as ISO 8601 being after the issue creation date will not make it stale , rotten or close it when it is old enough and days-before-close is set to 0', async () => {
|
||||
expect.assertions(3);
|
||||
const january2021 = '2021-01-01T00:00:00Z';
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 0,
|
||||
startDate: january2021.toString()
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
opts,
|
||||
1,
|
||||
'An issue with no label',
|
||||
'2020-01-01T17:00:00Z',
|
||||
'2020-01-01T17:00:00Z'
|
||||
)
|
||||
];
|
||||
const processor = new IssuesProcessorMock(
|
||||
opts,
|
||||
alwaysFalseStateMock,
|
||||
async p => (p === 1 ? TestIssueList : []),
|
||||
async () => [],
|
||||
async () => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toStrictEqual(0);
|
||||
expect(processor.rottenIssues.length).toStrictEqual(0);
|
||||
expect(processor.closedIssues.length).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('processing an issue with no label and a start date as RFC 2822 being before the issue creation date will make it stale and close it when it is old enough and days-before-close is set to 0', async () => {
|
||||
expect.assertions(2);
|
||||
const january2000 = 'January 1, 2000 00:00:00';
|
||||
@ -290,6 +325,7 @@ test('processing an issue with no label will make it stale and close it, if it i
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 1,
|
||||
daysBeforeRotten: 0,
|
||||
daysBeforeIssueClose: 0
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -307,6 +343,7 @@ test('processing an issue with no label will make it stale and close it, if it i
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.deletedBranchIssues).toHaveLength(0);
|
||||
});
|
||||
@ -459,10 +496,11 @@ test('processing an issue with no label will make it stale but not close it', as
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale issue will close it', async () => {
|
||||
test('processing a stale issue will rot it but not close it, given days before rotten is > -1', async () => {
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30
|
||||
daysBeforeClose: 30,
|
||||
daysBeforeRotten: 0
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
@ -488,13 +526,15 @@ test('processing a stale issue will close it', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale issue containing a space in the label will close it', async () => {
|
||||
test('processing a stale issue containing a space in the label will rotten it but not close it, given days before rotten is > -1', async () => {
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
staleIssueLabel: 'state: stale'
|
||||
staleIssueLabel: 'state: stale',
|
||||
daysBeforeRotten: 0
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
@ -520,13 +560,15 @@ test('processing a stale issue containing a space in the label will close it', a
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale issue containing a slash in the label will close it', async () => {
|
||||
test('processing a stale issue containing a slash in the label will rotten it but not close it', async () => {
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
staleIssueLabel: 'lifecycle/stale'
|
||||
staleIssueLabel: 'lifecycle/stale',
|
||||
daysBeforeRotten: 0
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
@ -552,20 +594,21 @@ test('processing a stale issue containing a slash in the label will close it', a
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale issue will close it when days-before-issue-stale override days-before-stale', async () => {
|
||||
test('processing a stale issue will rotten it but not close it when days-before-issue-rotten override days-before-rotten', async () => {
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30,
|
||||
daysBeforeIssueStale: 30
|
||||
daysBeforeRotten: -1,
|
||||
daysBeforeIssueRotten: 30
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
opts,
|
||||
1,
|
||||
'A stale issue that should be closed',
|
||||
'A stale issue that should be rotten',
|
||||
'2020-01-01T17:00:00Z',
|
||||
'2020-01-01T17:00:00Z',
|
||||
false,
|
||||
@ -585,13 +628,14 @@ test('processing a stale issue will close it when days-before-issue-stale overri
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it', async () => {
|
||||
test('processing a stale PR will rotten it but not close it', async () => {
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30
|
||||
daysBeforePrRotten: 30
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
@ -617,13 +661,49 @@ test('processing a stale PR will close it', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it when days-before-pr-stale override days-before-stale', async () => {
|
||||
test('processing a stale PR will rotten it it when days-before-pr-rotten override days-before-rotten', async () => {
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeRotten: 30,
|
||||
daysBeforePrRotten: 30
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
opts,
|
||||
1,
|
||||
'A stale PR that should be closed',
|
||||
'2020-01-01T17:00:00Z',
|
||||
'2020-01-01T17:00:00Z',
|
||||
false,
|
||||
true,
|
||||
['Stale']
|
||||
)
|
||||
];
|
||||
const processor = new IssuesProcessorMock(
|
||||
opts,
|
||||
alwaysFalseStateMock,
|
||||
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.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale PR will rotten it but not close it when days-before-pr-stale override days-before-stale', async () => {
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30,
|
||||
daysBeforeRotten: 0,
|
||||
daysBeforePrClose: 30
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -650,13 +730,15 @@ test('processing a stale PR will close it when days-before-pr-stale override day
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale issue will close it even if configured not to mark as stale', async () => {
|
||||
test('processing a stale issue will rotten it even if configured not to mark as stale', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeStale: -1,
|
||||
daysBeforeRotten: 0,
|
||||
staleIssueMessage: ''
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -683,13 +765,16 @@ test('processing a stale issue will close it even if configured not to mark as s
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale issue will close it even if configured not to mark as stale when days-before-issue-stale override days-before-stale', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeStale: 0,
|
||||
daysBeforeRotten: 0,
|
||||
|
||||
daysBeforeIssueStale: -1,
|
||||
staleIssueMessage: ''
|
||||
};
|
||||
@ -717,13 +802,15 @@ test('processing a stale issue will close it even if configured not to mark as s
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it even if configured not to mark as stale', async () => {
|
||||
test('processing a stale PR will rotten it even if configured not to mark as stale', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeStale: -1,
|
||||
daysBeforeRotten: 0,
|
||||
stalePrMessage: ''
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -750,14 +837,52 @@ test('processing a stale PR will close it even if configured not to mark as stal
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it even if configured not to mark as stale or rotten', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeStale: -1,
|
||||
stalePrMessage: '',
|
||||
daysBeforeRotten: -1
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
opts,
|
||||
1,
|
||||
'An issue with no label',
|
||||
'2020-01-01T17:00:00Z',
|
||||
'2020-01-01T17:00:00Z',
|
||||
false,
|
||||
true,
|
||||
['Stale']
|
||||
)
|
||||
];
|
||||
const processor = new IssuesProcessorMock(
|
||||
opts,
|
||||
alwaysFalseStateMock,
|
||||
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.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it even if configured not to mark as stale when days-before-pr-stale override days-before-stale', async () => {
|
||||
test('processing a stale PR will rotten it even if configured not to mark as stale when days-before-pr-stale override days-before-stale', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeStale: 0,
|
||||
daysBeforePrStale: -1,
|
||||
daysBeforeRotten: 0,
|
||||
|
||||
stalePrMessage: ''
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -784,10 +909,11 @@ test('processing a stale PR will close it even if configured not to mark as stal
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('closed issues will not be marked stale', async () => {
|
||||
test('closed issues will not be marked stale or rotten', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
@ -812,10 +938,41 @@ test('closed issues will not be marked stale', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('stale closed issues will not be closed', async () => {
|
||||
test('rotten closed issues will not be closed', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
1,
|
||||
'A rotten closed issue',
|
||||
'2020-01-01T17:00:00Z',
|
||||
'2020-01-01T17:00:00Z',
|
||||
false,
|
||||
false,
|
||||
['Rotten'],
|
||||
true
|
||||
)
|
||||
];
|
||||
const processor = new IssuesProcessorMock(
|
||||
DefaultProcessorOptions,
|
||||
alwaysFalseStateMock,
|
||||
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.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('stale closed issues will not be closed or rotten', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
@ -841,10 +998,11 @@ test('stale closed issues will not be closed', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('closed prs will not be marked stale', async () => {
|
||||
test('closed prs will not be marked stale or rotten', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
@ -870,6 +1028,7 @@ test('closed prs will not be marked stale', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
@ -902,7 +1061,7 @@ test('stale closed prs will not be closed', async () => {
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('locked issues will not be marked stale', async () => {
|
||||
test('locked issues will not be marked stale or rotten', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
@ -927,10 +1086,11 @@ test('locked issues will not be marked stale', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('stale locked issues will not be closed', async () => {
|
||||
test('stale locked issues will not be rotten or closed', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
@ -957,10 +1117,42 @@ test('stale locked issues will not be closed', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('locked prs will not be marked stale', async () => {
|
||||
test('rotten locked issues will not be rotten or closed', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
1,
|
||||
'A stale locked issue that will not be closed',
|
||||
'2020-01-01T17:00:00Z',
|
||||
'2020-01-01T17:00:00Z',
|
||||
false,
|
||||
false,
|
||||
['Rotten'],
|
||||
false,
|
||||
true
|
||||
)
|
||||
];
|
||||
const processor = new IssuesProcessorMock(
|
||||
DefaultProcessorOptions,
|
||||
alwaysFalseStateMock,
|
||||
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.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('locked prs will not be marked stale or rotten', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
@ -985,10 +1177,11 @@ test('locked prs will not be marked stale', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('stale locked prs will not be closed', async () => {
|
||||
test('stale locked prs will not be rotten or closed', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
DefaultProcessorOptions,
|
||||
@ -1015,11 +1208,12 @@ test('stale locked prs will not be closed', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('exempt issue labels will not be marked stale', async () => {
|
||||
expect.assertions(3);
|
||||
test('exempt issue labels will not be marked stale or rotten', async () => {
|
||||
expect.assertions(4);
|
||||
const opts = {...DefaultProcessorOptions};
|
||||
opts.exemptIssueLabels = 'Exempt';
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -1046,11 +1240,12 @@ test('exempt issue labels will not be marked stale', async () => {
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toStrictEqual(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues.length).toStrictEqual(0);
|
||||
expect(processor.removedLabelIssues.length).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('exempt issue labels will not be marked stale (multi issue label with spaces)', async () => {
|
||||
test('exempt issue labels will not be marked stale or rotten (multi issue label with spaces)', async () => {
|
||||
const opts = {...DefaultProcessorOptions};
|
||||
opts.exemptIssueLabels = 'Exempt, Cool, None';
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -1077,6 +1272,7 @@ test('exempt issue labels will not be marked stale (multi issue label with space
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
@ -1210,7 +1406,11 @@ test('stale issues should not be closed if days is set to -1', async () => {
|
||||
});
|
||||
|
||||
test('stale label should be removed if a comment was added to a stale issue', async () => {
|
||||
const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true};
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
removeStaleWhenUpdated: true,
|
||||
daysBeforeRotten: 0
|
||||
};
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
opts,
|
||||
@ -1337,8 +1537,12 @@ test('when the option "labelsToRemoveWhenStale" is set, the labels should be rem
|
||||
expect(processor.removedLabelIssues).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('stale label should not be removed if a comment was added by the bot (and the issue should be closed)', async () => {
|
||||
const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true};
|
||||
test('stale label should not be removed if a comment was added by the bot, given that it does not get rotten', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
removeStaleWhenUpdated: true,
|
||||
daysBeforeRotten: -1
|
||||
};
|
||||
github.context.actor = 'abot';
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
@ -1372,6 +1576,7 @@ test('stale label should not be removed if a comment was added by the bot (and t
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.rottenIssues).toHaveLength(0);
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||
});
|
||||
@ -1442,10 +1647,10 @@ test('stale issues should not be closed until after the closed number of days',
|
||||
expect(processor.staleIssues).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('stale issues should be closed if the closed nubmer of days (additive) is also passed', async () => {
|
||||
test('stale issues should be rotten if the rotten nubmer of days (additive) is also passed', async () => {
|
||||
const opts = {...DefaultProcessorOptions};
|
||||
opts.daysBeforeStale = 5; // stale after 5 days
|
||||
opts.daysBeforeClose = 1; // closes after 6 days
|
||||
opts.daysBeforeRotten = 1; // rotten after 6 days
|
||||
const lastUpdate = new Date();
|
||||
lastUpdate.setDate(lastUpdate.getDate() - 7);
|
||||
const TestIssueList: Issue[] = [
|
||||
@ -1471,8 +1676,9 @@ test('stale issues should be closed if the closed nubmer of days (additive) is a
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.closedIssues).toHaveLength(1);
|
||||
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
expect(processor.rottenIssues).toHaveLength(1);
|
||||
expect(processor.removedLabelIssues).toHaveLength(1); // the stale label should be removed on rotten label being added
|
||||
expect(processor.staleIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
@ -1690,8 +1896,12 @@ test('send stale message on prs when stale-pr-message is not empty', async () =>
|
||||
);
|
||||
});
|
||||
|
||||
test('git branch is deleted when option is enabled', async () => {
|
||||
const opts = {...DefaultProcessorOptions, deleteBranch: true};
|
||||
test('git branch is deleted when option is enabled and days before rotten is set to -1', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
deleteBranch: true,
|
||||
daysBeforeRotten: -1
|
||||
};
|
||||
const isPullRequest = true;
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
@ -1721,8 +1931,12 @@ test('git branch is deleted when option is enabled', async () => {
|
||||
expect(processor.deletedBranchIssues).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('git branch is not deleted when issue is not pull request', async () => {
|
||||
const opts = {...DefaultProcessorOptions, deleteBranch: true};
|
||||
test('git branch is not deleted when issue is not pull request and days before rotten is set to -1', async () => {
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
deleteBranch: true,
|
||||
daysBeforeRotten: -1
|
||||
};
|
||||
const isPullRequest = false;
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
@ -2516,13 +2730,14 @@ test('processing a locked issue with a close label will not remove the close lab
|
||||
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing an issue stale since less than the daysBeforeStale with a stale label created after daysBeforeClose should close the issue', async () => {
|
||||
expect.assertions(3);
|
||||
test('processing an issue stale since less than the daysBeforeStale with a stale label created after daysBeforeRotten should rotten the issue', async () => {
|
||||
expect.assertions(4);
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
staleIssueLabel: 'stale-label',
|
||||
daysBeforeStale: 30,
|
||||
daysBeforeClose: 7,
|
||||
daysBeforeRotten: 0,
|
||||
closeIssueMessage: 'close message',
|
||||
removeStaleWhenUpdated: false
|
||||
};
|
||||
@ -2554,9 +2769,10 @@ test('processing an issue stale since less than the daysBeforeStale with a stale
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||
expect(processor.removedLabelIssues).toHaveLength(1); // The stale label should be removed on adding the rotten label
|
||||
expect(processor.rottenIssues).toHaveLength(1); // Expected at 0 by the user
|
||||
expect(processor.deletedBranchIssues).toHaveLength(0);
|
||||
expect(processor.closedIssues).toHaveLength(1); // Expected at 0 by the user
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing an issue stale since less than the daysBeforeStale without a stale label should close the issue', async () => {
|
||||
@ -2566,6 +2782,8 @@ test('processing an issue stale since less than the daysBeforeStale without a st
|
||||
staleIssueLabel: 'stale-label',
|
||||
daysBeforeStale: 30,
|
||||
daysBeforeClose: 7,
|
||||
daysBeforeRotten: 0,
|
||||
|
||||
closeIssueMessage: 'close message',
|
||||
removeStaleWhenUpdated: false
|
||||
};
|
||||
@ -2601,13 +2819,14 @@ test('processing an issue stale since less than the daysBeforeStale without a st
|
||||
expect(processor.closedIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processing a pull request to be stale with the "stalePrMessage" option set will send a PR comment', async () => {
|
||||
test('processing a pull request to be stale with the "stalePrMessage" option set will send a PR comment, given that days before rotten is set to -1', async () => {
|
||||
expect.assertions(3);
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
stalePrMessage: 'This PR is stale',
|
||||
daysBeforeStale: 10,
|
||||
daysBeforePrStale: 1
|
||||
daysBeforePrStale: 1,
|
||||
daysBeforeRotten: -1
|
||||
};
|
||||
const issueDate = new Date();
|
||||
issueDate.setDate(issueDate.getDate() - 2);
|
||||
@ -2638,12 +2857,52 @@ test('processing a pull request to be stale with the "stalePrMessage" option set
|
||||
expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(1);
|
||||
});
|
||||
|
||||
test('processing a pull request to be stale with the "stalePrMessage" option set to empty will not send a PR comment', async () => {
|
||||
test('processing a pull request to be stale with the "stalePrMessage" option set will send two PR comments, given that days before rotten is set to 0', async () => {
|
||||
expect.assertions(3);
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
stalePrMessage: 'This PR is stale',
|
||||
daysBeforeStale: 10,
|
||||
daysBeforePrStale: 1,
|
||||
daysBeforeRotten: 0
|
||||
};
|
||||
const issueDate = new Date();
|
||||
issueDate.setDate(issueDate.getDate() - 2);
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
opts,
|
||||
1,
|
||||
'A pull request with no label and a stale message',
|
||||
issueDate.toDateString(),
|
||||
issueDate.toDateString(),
|
||||
false,
|
||||
true
|
||||
)
|
||||
];
|
||||
const processor = new IssuesProcessorMock(
|
||||
opts,
|
||||
alwaysFalseStateMock,
|
||||
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);
|
||||
expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(2);
|
||||
});
|
||||
|
||||
test('processing a pull request to be stale with the "stalePrMessage" option set to empty will not send a PR comment, given that "rottenPRMessage" is also an empty string and days before rotten is not -1', async () => {
|
||||
expect.assertions(3);
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
stalePrMessage: '',
|
||||
rottenPrMessage: '',
|
||||
daysBeforeStale: 10,
|
||||
daysBeforeRotten: 0,
|
||||
daysBeforePrStale: 1
|
||||
};
|
||||
const issueDate = new Date();
|
||||
@ -2675,6 +2934,45 @@ test('processing a pull request to be stale with the "stalePrMessage" option set
|
||||
expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('processing a pull request to be stale with the "stalePrMessage" option set to empty will send a PR comment from "rottenPRMessage" given that it is also an empty string', async () => {
|
||||
expect.assertions(3);
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
stalePrMessage: '',
|
||||
daysBeforeStale: 10,
|
||||
daysBeforeRotten: 0,
|
||||
|
||||
daysBeforePrStale: 1
|
||||
};
|
||||
const issueDate = new Date();
|
||||
issueDate.setDate(issueDate.getDate() - 2);
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
opts,
|
||||
1,
|
||||
'A pull request with no label and a stale message',
|
||||
issueDate.toDateString(),
|
||||
issueDate.toDateString(),
|
||||
false,
|
||||
true
|
||||
)
|
||||
];
|
||||
const processor = new IssuesProcessorMock(
|
||||
opts,
|
||||
alwaysFalseStateMock,
|
||||
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);
|
||||
expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(1);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@ -13,7 +13,7 @@ describe('operations-per-run option', (): void => {
|
||||
sut = new SUT();
|
||||
});
|
||||
|
||||
describe('when one issue should be stale within 10 days and updated 20 days ago', (): void => {
|
||||
describe('when one issue should be stale within 10 days and updated 20 days ago and days before rotten is -1', (): void => {
|
||||
beforeEach((): void => {
|
||||
sut.staleIn(10).newIssue().updated(20);
|
||||
});
|
||||
|
||||
@ -202,7 +202,7 @@ describe('state', (): void => {
|
||||
|
||||
await processor.processIssues(1);
|
||||
// make sure all issues are proceeded
|
||||
expect(infoSpy.mock.calls[71][0]).toContain(
|
||||
expect(infoSpy.mock.calls[77][0]).toContain(
|
||||
'No more issues found to process. Exiting...'
|
||||
);
|
||||
|
||||
|
||||
56
action.yml
56
action.yml
@ -1,6 +1,6 @@
|
||||
name: 'Close Stale Issues'
|
||||
name: 'Close, Rotten and Stale Issues'
|
||||
description: 'Close issues and pull requests with no recent activity'
|
||||
author: 'GitHub'
|
||||
author: 'M Viswanath Sai'
|
||||
inputs:
|
||||
repo-token:
|
||||
description: 'Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`.'
|
||||
@ -12,6 +12,12 @@ inputs:
|
||||
stale-pr-message:
|
||||
description: 'The message to post on the pull request when tagging it. If none provided, will not mark pull requests stale.'
|
||||
required: false
|
||||
rotten-issue-message:
|
||||
description: 'The message to post on the issue when tagging it. If none provided, will not mark issues rotten.'
|
||||
required: false
|
||||
rotten-pr-message:
|
||||
description: 'The message to post on the pull request when tagging it. If none provided, will not mark pull requests rotten.'
|
||||
required: false
|
||||
close-issue-message:
|
||||
description: 'The message to post on the issue when closing it. If none provided, will not comment when closing an issue.'
|
||||
required: false
|
||||
@ -21,17 +27,27 @@ inputs:
|
||||
days-before-stale:
|
||||
description: 'The number of days old an issue or a pull request can be before marking it stale. Set to -1 to never mark issues or pull requests as stale automatically.'
|
||||
required: false
|
||||
default: '60'
|
||||
default: '90'
|
||||
days-before-issue-stale:
|
||||
description: 'The number of days old an issue can be before marking it stale. Set to -1 to never mark issues as stale automatically. Override "days-before-stale" option regarding only the issues.'
|
||||
required: false
|
||||
days-before-pr-stale:
|
||||
description: 'The number of days old a pull request can be before marking it stale. Set to -1 to never mark pull requests as stale automatically. Override "days-before-stale" option regarding only the pull requests.'
|
||||
required: false
|
||||
days-before-rotten:
|
||||
description: 'The number of days old an issue or a pull request can be before marking it rotten. Set to -1 to never mark issues or pull requests as rotten automatically.'
|
||||
required: false
|
||||
default: '30'
|
||||
days-before-issue-rotten:
|
||||
description: 'The number of days old an issue can be before marking it rotten. Set to -1 to never mark issues as rotten automatically. Override "days-before-rotten" option regarding only the issues.'
|
||||
required: false
|
||||
days-before-pr-rotten:
|
||||
description: 'The number of days old a pull request can be before marking it rotten. Set to -1 to never mark pull requests as rotten automatically. Override "days-before-rotten" option regarding only the pull requests.'
|
||||
required: false
|
||||
days-before-close:
|
||||
description: 'The number of days to wait to close an issue or a pull request after it being marked stale. Set to -1 to never close stale issues or pull requests.'
|
||||
required: false
|
||||
default: '7'
|
||||
default: '30'
|
||||
days-before-issue-close:
|
||||
description: 'The number of days to wait to close an issue after it being marked stale. Set to -1 to never close stale issues. Override "days-before-close" option regarding only the issues.'
|
||||
required: false
|
||||
@ -42,6 +58,10 @@ inputs:
|
||||
description: 'The label to apply when an issue is stale.'
|
||||
required: false
|
||||
default: 'Stale'
|
||||
rotten-issue-label:
|
||||
description: 'The label to apply when an issue is rotten.'
|
||||
required: false
|
||||
default: 'Rotten'
|
||||
close-issue-label:
|
||||
description: 'The label to apply when an issue is closed.'
|
||||
required: false
|
||||
@ -57,6 +77,10 @@ inputs:
|
||||
description: 'The label to apply when a pull request is stale.'
|
||||
default: 'Stale'
|
||||
required: false
|
||||
rotten-pr-label:
|
||||
description: 'The label to apply when a pull request is rotten.'
|
||||
default: 'Rotten'
|
||||
required: false
|
||||
close-pr-label:
|
||||
description: 'The label to apply when a pull request is closed.'
|
||||
required: false
|
||||
@ -128,6 +152,18 @@ inputs:
|
||||
description: 'Remove stale labels from pull requests when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the pull requests.'
|
||||
default: ''
|
||||
required: false
|
||||
remove-rotten-when-updated:
|
||||
description: 'Remove rotten labels from issues and pull requests when they are updated or commented on.'
|
||||
default: 'true'
|
||||
required: false
|
||||
remove-issue-rotten-when-updated:
|
||||
description: 'Remove rotten labels from issues when they are updated or commented on. Override "remove-rotten-when-updated" option regarding only the issues.'
|
||||
default: ''
|
||||
required: false
|
||||
remove-pr-rotten-when-updated:
|
||||
description: 'Remove rotten labels from pull requests when they are updated or commented on. Override "remove-rotten-when-updated" option regarding only the pull requests.'
|
||||
default: ''
|
||||
required: false
|
||||
debug-only:
|
||||
description: 'Run the processor in debug mode without actually performing any operations on live issues.'
|
||||
default: 'false'
|
||||
@ -188,6 +224,18 @@ inputs:
|
||||
description: 'A comma delimited list of labels to remove when an issue or pull request becomes unstale.'
|
||||
default: ''
|
||||
required: false
|
||||
labels-to-add-when-unrotten:
|
||||
description: 'A comma delimited list of labels to add when an issue or pull request becomes unrotten.'
|
||||
default: ''
|
||||
required: false
|
||||
labels-to-remove-when-rotten:
|
||||
description: 'A comma delimited list of labels to remove when an issue or pull request becomes rotten.'
|
||||
default: ''
|
||||
required: false
|
||||
labels-to-remove-when-unrotten:
|
||||
description: 'A comma delimited list of labels to remove when an issue or pull request becomes unrotten.'
|
||||
default: ''
|
||||
required: false
|
||||
ignore-updates:
|
||||
description: 'Any update (update/comment) can reset the stale idle time on the issues and pull requests.'
|
||||
default: 'false'
|
||||
|
||||
418
dist/index.js
vendored
418
dist/index.js
vendored
@ -288,7 +288,9 @@ class Issue {
|
||||
this.milestone = issue.milestone;
|
||||
this.assignees = issue.assignees || [];
|
||||
this.isStale = (0, is_labeled_1.isLabeled)(this, this.staleLabel);
|
||||
this.isRotten = (0, is_labeled_1.isLabeled)(this, this.rottenLabel);
|
||||
this.markedStaleThisRun = false;
|
||||
this.markedRottenThisRun = false;
|
||||
}
|
||||
get isPullRequest() {
|
||||
return (0, is_pull_request_1.isPullRequest)(this);
|
||||
@ -296,6 +298,9 @@ class Issue {
|
||||
get staleLabel() {
|
||||
return this._getStaleLabel();
|
||||
}
|
||||
get rottenLabel() {
|
||||
return this._getRottenLabel();
|
||||
}
|
||||
get hasAssignees() {
|
||||
return this.assignees.length > 0;
|
||||
}
|
||||
@ -304,6 +309,11 @@ class Issue {
|
||||
? this._options.stalePrLabel
|
||||
: this._options.staleIssueLabel;
|
||||
}
|
||||
_getRottenLabel() {
|
||||
return this.isPullRequest
|
||||
? this._options.rottenPrLabel
|
||||
: this._options.rottenIssueLabel;
|
||||
}
|
||||
}
|
||||
exports.Issue = Issue;
|
||||
function mapLabels(labels) {
|
||||
@ -403,6 +413,7 @@ class IssuesProcessor {
|
||||
}
|
||||
constructor(options, state) {
|
||||
this.staleIssues = [];
|
||||
this.rottenIssues = [];
|
||||
this.closedIssues = [];
|
||||
this.deletedBranchIssues = [];
|
||||
this.removedLabelIssues = [];
|
||||
@ -439,6 +450,9 @@ class IssuesProcessor {
|
||||
const labelsToRemoveWhenStale = (0, words_to_list_1.wordsToList)(this.options.labelsToRemoveWhenStale);
|
||||
const labelsToAddWhenUnstale = (0, words_to_list_1.wordsToList)(this.options.labelsToAddWhenUnstale);
|
||||
const labelsToRemoveWhenUnstale = (0, words_to_list_1.wordsToList)(this.options.labelsToRemoveWhenUnstale);
|
||||
const labelsToRemoveWhenRotten = (0, words_to_list_1.wordsToList)(this.options.labelsToRemoveWhenRotten);
|
||||
const labelsToAddWhenUnrotten = (0, words_to_list_1.wordsToList)(this.options.labelsToAddWhenUnrotten);
|
||||
const labelsToRemoveWhenUnrotten = (0, words_to_list_1.wordsToList)(this.options.labelsToRemoveWhenUnrotten);
|
||||
for (const issue of issues.values()) {
|
||||
// Stop the processing if no more operations remains
|
||||
if (!this.operations.hasRemainingOperations()) {
|
||||
@ -450,7 +464,7 @@ class IssuesProcessor {
|
||||
continue;
|
||||
}
|
||||
yield issueLogger.grouping(`$$type #${issue.number}`, () => __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.processIssue(issue, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale);
|
||||
yield this.processIssue(issue, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale, labelsToAddWhenUnrotten, labelsToRemoveWhenUnrotten, labelsToRemoveWhenRotten);
|
||||
}));
|
||||
this.state.addIssueToProcessed(issue);
|
||||
}
|
||||
@ -465,7 +479,7 @@ class IssuesProcessor {
|
||||
return this.processIssues(page + 1);
|
||||
});
|
||||
}
|
||||
processIssue(issue, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale) {
|
||||
processIssue(issue, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale, labelsToAddWhenUnrotten, labelsToRemoveWhenUnrotten, labelsToRemoveWhenRotten) {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
(_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementProcessedItemsCount(issue);
|
||||
@ -475,12 +489,21 @@ class IssuesProcessor {
|
||||
const staleMessage = issue.isPullRequest
|
||||
? this.options.stalePrMessage
|
||||
: this.options.staleIssueMessage;
|
||||
const rottenMessage = issue.isPullRequest
|
||||
? this.options.rottenPrMessage
|
||||
: this.options.rottenIssueMessage;
|
||||
const closeMessage = issue.isPullRequest
|
||||
? this.options.closePrMessage
|
||||
: this.options.closeIssueMessage;
|
||||
const skipRottenMessage = issue.isPullRequest
|
||||
? this.options.rottenPrMessage.length === 0
|
||||
: this.options.rottenIssueMessage.length === 0;
|
||||
const staleLabel = issue.isPullRequest
|
||||
? this.options.stalePrLabel
|
||||
: this.options.staleIssueLabel;
|
||||
const rottenLabel = issue.isPullRequest
|
||||
? this.options.rottenPrLabel
|
||||
: this.options.rottenIssueLabel;
|
||||
const closeLabel = issue.isPullRequest
|
||||
? this.options.closePrLabel
|
||||
: this.options.closeIssueLabel;
|
||||
@ -546,11 +569,18 @@ class IssuesProcessor {
|
||||
return; // Don't process issues which were created before the start date
|
||||
}
|
||||
}
|
||||
// Check if the issue is stale, if not, check if it is rotten and then log the findings.
|
||||
if (issue.isStale) {
|
||||
issueLogger.info(`This $$type includes a stale label`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type does not include a stale label`);
|
||||
if (issue.isRotten) {
|
||||
issueLogger.info(`This $$type includes a rotten label`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type does not include a rotten label`);
|
||||
}
|
||||
}
|
||||
const exemptLabels = (0, words_to_list_1.wordsToList)(issue.isPullRequest
|
||||
? this.options.exemptPrLabels
|
||||
@ -601,51 +631,57 @@ class IssuesProcessor {
|
||||
IssuesProcessor._endIssueProcessing(issue);
|
||||
return; // Don't process draft PR
|
||||
}
|
||||
// Here we are looking into if the issue is stale or not, and then adding the label. This same code will also be used for the rotten label.
|
||||
// Determine if this issue needs to be marked stale first
|
||||
if (!issue.isStale) {
|
||||
issueLogger.info(`This $$type is not stale`);
|
||||
const shouldIgnoreUpdates = new ignore_updates_1.IgnoreUpdates(this.options, issue).shouldIgnoreUpdates();
|
||||
// Should this issue be marked as stale?
|
||||
let shouldBeStale;
|
||||
// Ignore the last update and only use the creation date
|
||||
if (shouldIgnoreUpdates) {
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(issue.created_at, daysBeforeStale);
|
||||
}
|
||||
// Use the last update to check if we need to stale
|
||||
else {
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(issue.updated_at, daysBeforeStale);
|
||||
}
|
||||
if (shouldBeStale) {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(`This $$type should be stale based on the creation date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.created_at))} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should be stale based on the last update date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.updated_at))} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||
}
|
||||
if (shouldMarkAsStale) {
|
||||
issueLogger.info(`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
||||
yield this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||
issue.isStale = true; // This issue is now considered stale
|
||||
issue.markedStaleThisRun = true;
|
||||
issueLogger.info(`This $$type is now stale`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
||||
}
|
||||
if (issue.isRotten) {
|
||||
yield this._processRottenIssue(issue, rottenLabel, rottenMessage, labelsToAddWhenUnrotten, labelsToRemoveWhenUnrotten, labelsToRemoveWhenRotten, closeMessage, closeLabel);
|
||||
}
|
||||
else {
|
||||
const shouldIgnoreUpdates = new ignore_updates_1.IgnoreUpdates(this.options, issue).shouldIgnoreUpdates();
|
||||
// Should this issue be marked as stale?
|
||||
let shouldBeStale;
|
||||
// Ignore the last update and only use the creation date
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(`This $$type should not be stale based on the creation date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.created_at))} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(issue.created_at, daysBeforeStale);
|
||||
}
|
||||
// Use the last update to check if we need to stale
|
||||
else {
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(issue.updated_at, daysBeforeStale);
|
||||
}
|
||||
if (shouldBeStale) {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(`This $$type should be stale based on the creation date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.created_at))} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should be stale based on the last update date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.updated_at))} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||
}
|
||||
if (shouldMarkAsStale) {
|
||||
issueLogger.info(`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
||||
yield this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||
issue.isStale = true; // This issue is now considered stale
|
||||
issue.markedStaleThisRun = true;
|
||||
issueLogger.info(`This $$type is now stale`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should not be stale based on the last update date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.updated_at))} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(`This $$type should not be stale based on the creation date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.created_at))} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should not be stale based on the last update date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.updated_at))} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process the issue if it was marked stale
|
||||
if (issue.isStale) {
|
||||
issueLogger.info(`This $$type is already stale`);
|
||||
yield this._processStaleIssue(issue, staleLabel, staleMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale, closeMessage, closeLabel);
|
||||
yield this._processStaleIssue(issue, staleLabel, staleMessage, rottenLabel, rottenMessage, closeLabel, closeMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale, labelsToAddWhenUnrotten, labelsToRemoveWhenUnrotten, labelsToRemoveWhenRotten, skipRottenMessage);
|
||||
}
|
||||
IssuesProcessor._endIssueProcessing(issue);
|
||||
});
|
||||
@ -752,17 +788,23 @@ class IssuesProcessor {
|
||||
});
|
||||
}
|
||||
// handle all of the stale issue logic when we find a stale issue
|
||||
_processStaleIssue(issue, staleLabel, staleMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale, closeMessage, closeLabel) {
|
||||
// This whole thing needs to be altered, to be calculated based on the days to rotten, rather than days to close or whatever
|
||||
_processStaleIssue(issue, staleLabel, staleMessage, rottenLabel, rottenMessage, closeLabel, closeMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale, labelsToAddWhenUnrotten, labelsToRemoveWhenUnrotten, labelsToRemoveWhenRotten, skipMessage) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||
let issueHasClosed = false;
|
||||
// We can get the label creation date from the getLableCreationDate function
|
||||
const markedStaleOn = (yield this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
|
||||
issueLogger.info(`$$type marked stale on: ${logger_service_1.LoggerService.cyan(markedStaleOn)}`);
|
||||
const issueHasCommentsSinceStale = yield this._hasCommentsSince(issue, markedStaleOn, staleMessage);
|
||||
issueLogger.info(`$$type has been commented on: ${logger_service_1.LoggerService.cyan(issueHasCommentsSinceStale)}`);
|
||||
const daysBeforeRotten = issue.isPullRequest
|
||||
? this._getDaysBeforePrRotten()
|
||||
: this._getDaysBeforeIssueRotten();
|
||||
const daysBeforeClose = issue.isPullRequest
|
||||
? this._getDaysBeforePrClose()
|
||||
: this._getDaysBeforeIssueClose();
|
||||
issueLogger.info(`Days before $$type close: ${logger_service_1.LoggerService.cyan(daysBeforeClose)}`);
|
||||
issueLogger.info(`Days before $$type rotten: ${logger_service_1.LoggerService.cyan(daysBeforeRotten)}`);
|
||||
const shouldRemoveStaleWhenUpdated = this._shouldRemoveStaleWhenUpdated(issue);
|
||||
issueLogger.info(`The option ${issueLogger.createOptionLink(this._getRemoveStaleWhenUpdatedUsedOptionName(issue))} is: ${logger_service_1.LoggerService.cyan(shouldRemoveStaleWhenUpdated)}`);
|
||||
if (shouldRemoveStaleWhenUpdated) {
|
||||
@ -771,6 +813,7 @@ class IssuesProcessor {
|
||||
else {
|
||||
issueLogger.info(`The stale label should be removed if all conditions met`);
|
||||
}
|
||||
// we will need to use a variation of this for the rotten state
|
||||
if (issue.markedStaleThisRun) {
|
||||
issueLogger.info(`marked stale this run, so don't check for updates`);
|
||||
yield this._removeLabelsOnStatusTransition(issue, labelsToRemoveWhenStale, option_1.Option.LabelsToRemoveWhenStale);
|
||||
@ -791,13 +834,124 @@ class IssuesProcessor {
|
||||
issueLogger.info(`Skipping the process since the $$type is now un-stale`);
|
||||
return; // Nothing to do because it is no longer stale
|
||||
}
|
||||
if (daysBeforeRotten < 0) {
|
||||
if (daysBeforeClose < 0) {
|
||||
issueLogger.info(`Stale $$type cannot be rotten or closed because days before rotten: ${daysBeforeRotten}, and days before close: ${daysBeforeClose}`);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`Closing issue without rottening it because days before $$type rotten: ${logger_service_1.LoggerService.cyan(daysBeforeRotten)}`);
|
||||
const issueHasUpdateInCloseWindow = IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose);
|
||||
issueLogger.info(`$$type has been updated in the last ${daysBeforeClose} days: ${logger_service_1.LoggerService.cyan(issueHasUpdateInCloseWindow)}`);
|
||||
if (!issueHasUpdateInCloseWindow && !issueHasCommentsSinceStale) {
|
||||
issueLogger.info(`Closing $$type because it was last updated on: ${logger_service_1.LoggerService.cyan(issue.updated_at)}`);
|
||||
yield this._closeIssue(issue, closeMessage, closeLabel);
|
||||
issueHasClosed = true;
|
||||
if (this.options.deleteBranch && issue.pull_request) {
|
||||
issueLogger.info(`Deleting the branch since the option ${issueLogger.createOptionLink(option_1.Option.DeleteBranch)} is enabled`);
|
||||
yield this._deleteBranch(issue);
|
||||
this.deletedBranchIssues.push(issue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`Stale $$type is not old enough to close yet (hasComments? ${issueHasCommentsSinceStale}, hasUpdate? ${issueHasUpdateInCloseWindow})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: make a function for shouldMarkWhenRotten
|
||||
const shouldMarkAsRotten = (0, should_mark_when_stale_1.shouldMarkWhenStale)(daysBeforeRotten);
|
||||
if (issueHasClosed) {
|
||||
issueLogger.info(`Issue $$type has been closed, no need to process it further.`);
|
||||
return;
|
||||
}
|
||||
if (!issue.isRotten) {
|
||||
issueLogger.info(`This $$type is not rotten`);
|
||||
const shouldIgnoreUpdates = new ignore_updates_1.IgnoreUpdates(this.options, issue).shouldIgnoreUpdates();
|
||||
const shouldBeRotten = !IssuesProcessor._updatedSince(issue.updated_at, daysBeforeRotten);
|
||||
if (shouldBeRotten) {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(`This $$type should be rotten based on the creation date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.created_at))} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should be rotten based on the last update date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.updated_at))} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||
}
|
||||
if (shouldMarkAsRotten) {
|
||||
issueLogger.info(`This $$type should be marked as rotten based on the option ${issueLogger.createOptionLink(this._getDaysBeforeRottenUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeRotten)})`);
|
||||
// remove the stale label before marking the issue as rotten
|
||||
yield this._removeStaleLabel(issue, staleLabel);
|
||||
yield this._markRotten(issue, rottenMessage, rottenLabel, skipMessage);
|
||||
issue.isRotten = true; // This issue is now considered rotten
|
||||
issue.markedRottenThisRun = true;
|
||||
issueLogger.info(`This $$type is now rotten`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type should not be marked as rotten based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeRotten)})`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(`This $$type is not old enough to be rotten based on the creation date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.created_at))} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`This $$type is not old enough to be rotten based on the creation date the ${(0, get_humanized_date_1.getHumanizedDate)(new Date(issue.updated_at))} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (issue.isRotten) {
|
||||
issueLogger.info(`This $$type is already rotten`);
|
||||
// process the rotten issues
|
||||
this._processRottenIssue(issue, rottenLabel, rottenMessage, labelsToAddWhenUnrotten, labelsToRemoveWhenUnrotten, labelsToRemoveWhenRotten, closeMessage, closeLabel);
|
||||
}
|
||||
});
|
||||
}
|
||||
_processRottenIssue(issue, rottenLabel, rottenMessage, labelsToAddWhenUnrotten, labelsToRemoveWhenUnrotten, labelsToRemoveWhenRotten, closeMessage, closeLabel) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||
// We can get the label creation date from the getLableCreationDate function
|
||||
const markedRottenOn = (yield this.getLabelCreationDate(issue, rottenLabel)) || issue.updated_at;
|
||||
issueLogger.info(`$$type marked rotten on: ${logger_service_1.LoggerService.cyan(markedRottenOn)}`);
|
||||
const issueHasCommentsSinceRotten = yield this._hasCommentsSince(issue, markedRottenOn, rottenMessage);
|
||||
issueLogger.info(`$$type has been commented on: ${logger_service_1.LoggerService.cyan(issueHasCommentsSinceRotten)}`);
|
||||
const daysBeforeClose = issue.isPullRequest
|
||||
? this._getDaysBeforePrClose()
|
||||
: this._getDaysBeforeIssueClose();
|
||||
issueLogger.info(`Days before $$type close: ${logger_service_1.LoggerService.cyan(daysBeforeClose)}`);
|
||||
const shouldRemoveRottenWhenUpdated = this._shouldRemoveRottenWhenUpdated(issue);
|
||||
issueLogger.info(`The option ${issueLogger.createOptionLink(this._getRemoveRottenWhenUpdatedUsedOptionName(issue))} is: ${logger_service_1.LoggerService.cyan(shouldRemoveRottenWhenUpdated)}`);
|
||||
if (shouldRemoveRottenWhenUpdated) {
|
||||
issueLogger.info(`The rotten label should not be removed`);
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`The rotten label should be removed if all conditions met`);
|
||||
}
|
||||
if (issue.markedRottenThisRun) {
|
||||
issueLogger.info(`marked rotten this run, so don't check for updates`);
|
||||
yield this._removeLabelsOnStatusTransition(issue, labelsToRemoveWhenRotten, option_1.Option.LabelsToRemoveWhenRotten);
|
||||
}
|
||||
// The issue.updated_at and markedRottenOn 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 issueHasUpdateSinceRotten = (0, is_date_more_recent_than_1.isDateMoreRecentThan)(new Date(issue.updated_at), new Date(markedRottenOn), 15);
|
||||
issueLogger.info(`$$type has been updated since it was marked rotten: ${logger_service_1.LoggerService.cyan(issueHasUpdateSinceRotten)}`);
|
||||
// Should we un-rotten this issue?
|
||||
if (shouldRemoveRottenWhenUpdated &&
|
||||
(issueHasUpdateSinceRotten || issueHasCommentsSinceRotten) &&
|
||||
!issue.markedRottenThisRun) {
|
||||
issueLogger.info(`Remove the rotten label since the $$type has been updated and the workflow should remove the stale label when updated`);
|
||||
yield this._removeRottenLabel(issue, rottenLabel);
|
||||
// Are there labels to remove or add when an issue is no longer rotten?
|
||||
// This logic takes care of removing labels when unrotten
|
||||
yield this._removeLabelsOnStatusTransition(issue, labelsToRemoveWhenUnrotten, option_1.Option.LabelsToRemoveWhenUnrotten);
|
||||
yield this._addLabelsWhenUnrotten(issue, labelsToAddWhenUnrotten);
|
||||
issueLogger.info(`Skipping the process since the $$type is now un-rotten`);
|
||||
return; // Nothing to do because it is no longer rotten
|
||||
}
|
||||
// Now start closing logic
|
||||
if (daysBeforeClose < 0) {
|
||||
return; // Nothing to do because we aren't closing stale issues
|
||||
return; // Nothing to do because we aren't closing rotten issues
|
||||
}
|
||||
const issueHasUpdateInCloseWindow = IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose);
|
||||
issueLogger.info(`$$type has been updated in the last ${daysBeforeClose} days: ${logger_service_1.LoggerService.cyan(issueHasUpdateInCloseWindow)}`);
|
||||
if (!issueHasCommentsSinceStale && !issueHasUpdateInCloseWindow) {
|
||||
if (!issueHasCommentsSinceRotten && !issueHasUpdateInCloseWindow) {
|
||||
issueLogger.info(`Closing $$type because it was last updated on: ${logger_service_1.LoggerService.cyan(issue.updated_at)}`);
|
||||
yield this._closeIssue(issue, closeMessage, closeLabel);
|
||||
if (this.options.deleteBranch && issue.pull_request) {
|
||||
@ -807,7 +961,7 @@ class IssuesProcessor {
|
||||
}
|
||||
}
|
||||
else {
|
||||
issueLogger.info(`Stale $$type is not old enough to close yet (hasComments? ${issueHasCommentsSinceStale}, hasUpdate? ${issueHasUpdateInCloseWindow})`);
|
||||
issueLogger.info(`Rotten $$type is not old enough to close yet (hasComments? ${issueHasCommentsSinceRotten}, hasUpdate? ${issueHasUpdateInCloseWindow})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -877,12 +1031,57 @@ class IssuesProcessor {
|
||||
}
|
||||
});
|
||||
}
|
||||
_markRotten(issue, rottenMessage, rottenLabel, skipMessage) {
|
||||
var _a, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||
issueLogger.info(`Marking this $$type as rotten`);
|
||||
this.rottenIssues.push(issue);
|
||||
// if the issue is being marked rotten, the updated date should be changed to right now
|
||||
// so that close calculations work correctly
|
||||
const newUpdatedAtDate = new Date();
|
||||
issue.updated_at = newUpdatedAtDate.toString();
|
||||
if (!skipMessage) {
|
||||
try {
|
||||
this._consumeIssueOperation(issue);
|
||||
(_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedItemsComment(issue);
|
||||
if (!this.options.debugOnly) {
|
||||
yield this.client.rest.issues.createComment({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: rottenMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
issueLogger.error(`Error when creating a comment: ${error.message}`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
this._consumeIssueOperation(issue);
|
||||
(_b = this.statistics) === null || _b === void 0 ? void 0 : _b.incrementAddedItemsLabel(issue);
|
||||
(_c = this.statistics) === null || _c === void 0 ? void 0 : _c.incrementStaleItemsCount(issue);
|
||||
if (!this.options.debugOnly) {
|
||||
yield this.client.rest.issues.addLabels({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [rottenLabel]
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
issueLogger.error(`Error when adding a label: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Close an issue based on staleness
|
||||
_closeIssue(issue, closeMessage, closeLabel) {
|
||||
var _a, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||
issueLogger.info(`Closing $$type for being stale`);
|
||||
issueLogger.info(`Closing $$type for being stale/rotten`);
|
||||
this.closedIssues.push(issue);
|
||||
if (closeMessage) {
|
||||
try {
|
||||
@ -1012,6 +1211,16 @@ class IssuesProcessor {
|
||||
? this.options.daysBeforeStale
|
||||
: this.options.daysBeforePrStale;
|
||||
}
|
||||
_getDaysBeforeIssueRotten() {
|
||||
return isNaN(this.options.daysBeforeIssueRotten)
|
||||
? this.options.daysBeforeRotten
|
||||
: this.options.daysBeforeIssueRotten;
|
||||
}
|
||||
_getDaysBeforePrRotten() {
|
||||
return isNaN(this.options.daysBeforePrRotten)
|
||||
? this.options.daysBeforeRotten
|
||||
: this.options.daysBeforePrRotten;
|
||||
}
|
||||
_getDaysBeforeIssueClose() {
|
||||
return isNaN(this.options.daysBeforeIssueClose)
|
||||
? this.options.daysBeforeClose
|
||||
@ -1063,6 +1272,18 @@ class IssuesProcessor {
|
||||
}
|
||||
return this.options.removeStaleWhenUpdated;
|
||||
}
|
||||
_shouldRemoveRottenWhenUpdated(issue) {
|
||||
if (issue.isPullRequest) {
|
||||
if ((0, is_boolean_1.isBoolean)(this.options.removePrRottenWhenUpdated)) {
|
||||
return this.options.removePrRottenWhenUpdated;
|
||||
}
|
||||
return this.options.removeRottenWhenUpdated;
|
||||
}
|
||||
if ((0, is_boolean_1.isBoolean)(this.options.removeIssueRottenWhenUpdated)) {
|
||||
return this.options.removeIssueRottenWhenUpdated;
|
||||
}
|
||||
return this.options.removeRottenWhenUpdated;
|
||||
}
|
||||
_removeLabelsOnStatusTransition(issue, removeLabels, staleStatus) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (!removeLabels.length) {
|
||||
@ -1101,6 +1322,33 @@ class IssuesProcessor {
|
||||
}
|
||||
});
|
||||
}
|
||||
_addLabelsWhenUnrotten(issue, labelsToAdd) {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (!labelsToAdd.length) {
|
||||
return;
|
||||
}
|
||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||
issueLogger.info(`Adding all the labels specified via the ${this._logger.createOptionLink(option_1.Option.LabelsToAddWhenUnrotten)} option.`);
|
||||
// TODO: this might need to be changed to a set to avoiod repetition
|
||||
this.addedLabelIssues.push(issue);
|
||||
try {
|
||||
this._consumeIssueOperation(issue);
|
||||
(_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedItemsLabel(issue);
|
||||
if (!this.options.debugOnly) {
|
||||
yield this.client.rest.issues.addLabels({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: labelsToAdd
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this._logger.error(`Error when adding labels after updated from rotten: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
_removeStaleLabel(issue, staleLabel) {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
@ -1110,6 +1358,15 @@ class IssuesProcessor {
|
||||
(_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementUndoStaleItemsCount(issue);
|
||||
});
|
||||
}
|
||||
_removeRottenLabel(issue, rottenLabel) {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||
issueLogger.info(`The $$type is no longer rotten. Removing the rotten label...`);
|
||||
yield this._removeLabel(issue, rottenLabel);
|
||||
(_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementUndoRottenItemsCount(issue);
|
||||
});
|
||||
}
|
||||
_removeCloseLabel(issue, closeLabel) {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
@ -1150,6 +1407,21 @@ class IssuesProcessor {
|
||||
? option_1.Option.DaysBeforeStale
|
||||
: option_1.Option.DaysBeforePrStale;
|
||||
}
|
||||
_getDaysBeforeRottenUsedOptionName(issue) {
|
||||
return issue.isPullRequest
|
||||
? this._getDaysBeforePrRottenUsedOptionName()
|
||||
: this._getDaysBeforeIssueRottenUsedOptionName();
|
||||
}
|
||||
_getDaysBeforeIssueRottenUsedOptionName() {
|
||||
return isNaN(this.options.daysBeforeIssueRotten)
|
||||
? option_1.Option.DaysBeforeRotten
|
||||
: option_1.Option.DaysBeforeIssueRotten;
|
||||
}
|
||||
_getDaysBeforePrRottenUsedOptionName() {
|
||||
return isNaN(this.options.daysBeforePrRotten)
|
||||
? option_1.Option.DaysBeforeRotten
|
||||
: option_1.Option.DaysBeforePrRotten;
|
||||
}
|
||||
_getRemoveStaleWhenUpdatedUsedOptionName(issue) {
|
||||
if (issue.isPullRequest) {
|
||||
if ((0, is_boolean_1.isBoolean)(this.options.removePrStaleWhenUpdated)) {
|
||||
@ -1162,6 +1434,18 @@ class IssuesProcessor {
|
||||
}
|
||||
return option_1.Option.RemoveStaleWhenUpdated;
|
||||
}
|
||||
_getRemoveRottenWhenUpdatedUsedOptionName(issue) {
|
||||
if (issue.isPullRequest) {
|
||||
if ((0, is_boolean_1.isBoolean)(this.options.removePrRottenWhenUpdated)) {
|
||||
return option_1.Option.RemovePrRottenWhenUpdated;
|
||||
}
|
||||
return option_1.Option.RemoveRottenWhenUpdated;
|
||||
}
|
||||
if ((0, is_boolean_1.isBoolean)(this.options.removeIssueRottenWhenUpdated)) {
|
||||
return option_1.Option.RemoveIssueRottenWhenUpdated;
|
||||
}
|
||||
return option_1.Option.RemoveRottenWhenUpdated;
|
||||
}
|
||||
}
|
||||
exports.IssuesProcessor = IssuesProcessor;
|
||||
|
||||
@ -1815,6 +2099,10 @@ class Statistics {
|
||||
this.stalePullRequestsCount = 0;
|
||||
this.undoStaleIssuesCount = 0;
|
||||
this.undoStalePullRequestsCount = 0;
|
||||
this.rottenIssuesCount = 0;
|
||||
this.rottenPullRequestsCount = 0;
|
||||
this.undoRottenIssuesCount = 0;
|
||||
this.undoRottenPullRequestsCount = 0;
|
||||
this.operationsCount = 0;
|
||||
this.closedIssuesCount = 0;
|
||||
this.closedPullRequestsCount = 0;
|
||||
@ -1850,6 +2138,12 @@ class Statistics {
|
||||
}
|
||||
return this._incrementUndoStaleIssuesCount(increment);
|
||||
}
|
||||
incrementUndoRottenItemsCount(issue, increment = 1) {
|
||||
if (issue.isPullRequest) {
|
||||
return this._incrementUndoRottenPullRequestsCount(increment);
|
||||
}
|
||||
return this._incrementUndoRottenIssuesCount(increment);
|
||||
}
|
||||
setOperationsCount(operationsCount) {
|
||||
this.operationsCount = operationsCount;
|
||||
return this;
|
||||
@ -1942,6 +2236,14 @@ class Statistics {
|
||||
this.undoStaleIssuesCount += increment;
|
||||
return this;
|
||||
}
|
||||
_incrementUndoRottenPullRequestsCount(increment = 1) {
|
||||
this.undoRottenPullRequestsCount += increment;
|
||||
return this;
|
||||
}
|
||||
_incrementUndoRottenIssuesCount(increment = 1) {
|
||||
this.undoRottenIssuesCount += increment;
|
||||
return this;
|
||||
}
|
||||
_incrementUndoStalePullRequestsCount(increment = 1) {
|
||||
this.undoStalePullRequestsCount += increment;
|
||||
return this;
|
||||
@ -2175,18 +2477,25 @@ var Option;
|
||||
Option["RepoToken"] = "repo-token";
|
||||
Option["StaleIssueMessage"] = "stale-issue-message";
|
||||
Option["StalePrMessage"] = "stale-pr-message";
|
||||
Option["RottenIssueMessage"] = "rotten-issue-message";
|
||||
Option["RottenPrMessage"] = "rotten-pr-message";
|
||||
Option["CloseIssueMessage"] = "close-issue-message";
|
||||
Option["ClosePrMessage"] = "close-pr-message";
|
||||
Option["DaysBeforeStale"] = "days-before-stale";
|
||||
Option["DaysBeforeIssueStale"] = "days-before-issue-stale";
|
||||
Option["DaysBeforePrStale"] = "days-before-pr-stale";
|
||||
Option["DaysBeforeRotten"] = "days-before-rotten";
|
||||
Option["DaysBeforeIssueRotten"] = "days-before-issue-rotten";
|
||||
Option["DaysBeforePrRotten"] = "days-before-pr-rotten";
|
||||
Option["DaysBeforeClose"] = "days-before-close";
|
||||
Option["DaysBeforeIssueClose"] = "days-before-issue-close";
|
||||
Option["DaysBeforePrClose"] = "days-before-pr-close";
|
||||
Option["StaleIssueLabel"] = "stale-issue-label";
|
||||
Option["RottenIssueLabel"] = "rotten-issue-label";
|
||||
Option["CloseIssueLabel"] = "close-issue-label";
|
||||
Option["ExemptIssueLabels"] = "exempt-issue-labels";
|
||||
Option["StalePrLabel"] = "stale-pr-label";
|
||||
Option["RottenPrLabel"] = "rotten-pr-label";
|
||||
Option["ClosePrLabel"] = "close-pr-label";
|
||||
Option["ExemptPrLabels"] = "exempt-pr-labels";
|
||||
Option["OnlyLabels"] = "only-labels";
|
||||
@ -2197,6 +2506,9 @@ var Option;
|
||||
Option["RemoveStaleWhenUpdated"] = "remove-stale-when-updated";
|
||||
Option["RemoveIssueStaleWhenUpdated"] = "remove-issue-stale-when-updated";
|
||||
Option["RemovePrStaleWhenUpdated"] = "remove-pr-stale-when-updated";
|
||||
Option["RemoveRottenWhenUpdated"] = "remove-rotten-when-updated";
|
||||
Option["RemoveIssueRottenWhenUpdated"] = "remove-issue-rotten-when-updated";
|
||||
Option["RemovePrRottenWhenUpdated"] = "remove-pr-rotten-when-updated";
|
||||
Option["DebugOnly"] = "debug-only";
|
||||
Option["Ascending"] = "ascending";
|
||||
Option["DeleteBranch"] = "delete-branch";
|
||||
@ -2217,6 +2529,9 @@ var Option;
|
||||
Option["LabelsToRemoveWhenStale"] = "labels-to-remove-when-stale";
|
||||
Option["LabelsToRemoveWhenUnstale"] = "labels-to-remove-when-unstale";
|
||||
Option["LabelsToAddWhenUnstale"] = "labels-to-add-when-unstale";
|
||||
Option["LabelsToRemoveWhenRotten"] = "labels-to-remove-when-rotten";
|
||||
Option["LabelsToRemoveWhenUnrotten"] = "labels-to-remove-when-unrotten";
|
||||
Option["LabelsToAddWhenUnrotten"] = "labels-to-add-when-unrotten";
|
||||
Option["IgnoreUpdates"] = "ignore-updates";
|
||||
Option["IgnoreIssueUpdates"] = "ignore-issue-updates";
|
||||
Option["IgnorePrUpdates"] = "ignore-pr-updates";
|
||||
@ -2503,7 +2818,7 @@ function _run() {
|
||||
core.info(`Github API rate remaining: ${rateLimitAtEnd.remaining}; reset at: ${rateLimitAtEnd.reset}`);
|
||||
}
|
||||
yield state.persist();
|
||||
yield processOutput(issueProcessor.staleIssues, issueProcessor.closedIssues);
|
||||
yield processOutput(issueProcessor.staleIssues, issueProcessor.rottenIssues, issueProcessor.closedIssues);
|
||||
}
|
||||
catch (error) {
|
||||
core.error(error);
|
||||
@ -2516,18 +2831,25 @@ function _getAndValidateArgs() {
|
||||
repoToken: core.getInput('repo-token'),
|
||||
staleIssueMessage: core.getInput('stale-issue-message'),
|
||||
stalePrMessage: core.getInput('stale-pr-message'),
|
||||
rottenIssueMessage: core.getInput('rotten-issue-message'),
|
||||
rottenPrMessage: core.getInput('rotten-pr-message'),
|
||||
closeIssueMessage: core.getInput('close-issue-message'),
|
||||
closePrMessage: core.getInput('close-pr-message'),
|
||||
daysBeforeStale: parseFloat(core.getInput('days-before-stale', { required: true })),
|
||||
daysBeforeRotten: parseFloat(core.getInput('days-before-rotten', { required: true })),
|
||||
daysBeforeIssueStale: parseFloat(core.getInput('days-before-issue-stale')),
|
||||
daysBeforePrStale: parseFloat(core.getInput('days-before-pr-stale')),
|
||||
daysBeforeIssueRotten: parseFloat(core.getInput('days-before-issue-rotten')),
|
||||
daysBeforePrRotten: parseFloat(core.getInput('days-before-pr-rotten')),
|
||||
daysBeforeClose: parseInt(core.getInput('days-before-close', { required: true })),
|
||||
daysBeforeIssueClose: parseInt(core.getInput('days-before-issue-close')),
|
||||
daysBeforePrClose: parseInt(core.getInput('days-before-pr-close')),
|
||||
staleIssueLabel: core.getInput('stale-issue-label', { required: true }),
|
||||
rottenIssueLabel: core.getInput('rotten-issue-label', { required: true }),
|
||||
closeIssueLabel: core.getInput('close-issue-label'),
|
||||
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
||||
stalePrLabel: core.getInput('stale-pr-label', { required: true }),
|
||||
rottenPrLabel: core.getInput('rotten-pr-label', { required: true }),
|
||||
closePrLabel: core.getInput('close-pr-label'),
|
||||
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
||||
onlyLabels: core.getInput('only-labels'),
|
||||
@ -2540,6 +2862,9 @@ function _getAndValidateArgs() {
|
||||
removeStaleWhenUpdated: !(core.getInput('remove-stale-when-updated') === 'false'),
|
||||
removeIssueStaleWhenUpdated: _toOptionalBoolean('remove-issue-stale-when-updated'),
|
||||
removePrStaleWhenUpdated: _toOptionalBoolean('remove-pr-stale-when-updated'),
|
||||
removeRottenWhenUpdated: !(core.getInput('remove-rotten-when-updated') === 'false'),
|
||||
removeIssueRottenWhenUpdated: _toOptionalBoolean('remove-issue-rotten-when-updated'),
|
||||
removePrRottenWhenUpdated: _toOptionalBoolean('remove-pr-rotten-when-updated'),
|
||||
debugOnly: core.getInput('debug-only') === 'true',
|
||||
ascending: core.getInput('ascending') === 'true',
|
||||
deleteBranch: core.getInput('delete-branch') === 'true',
|
||||
@ -2562,6 +2887,9 @@ function _getAndValidateArgs() {
|
||||
labelsToRemoveWhenStale: core.getInput('labels-to-remove-when-stale'),
|
||||
labelsToRemoveWhenUnstale: core.getInput('labels-to-remove-when-unstale'),
|
||||
labelsToAddWhenUnstale: core.getInput('labels-to-add-when-unstale'),
|
||||
labelsToRemoveWhenRotten: core.getInput('labels-to-remove-when-rotten'),
|
||||
labelsToRemoveWhenUnrotten: core.getInput('labels-to-remove-when-unrotten'),
|
||||
labelsToAddWhenUnrotten: core.getInput('labels-to-add-when-unrotten'),
|
||||
ignoreUpdates: core.getInput('ignore-updates') === 'true',
|
||||
ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'),
|
||||
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
|
||||
@ -2576,6 +2904,13 @@ function _getAndValidateArgs() {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
for (const numberInput of ['days-before-rotten']) {
|
||||
if (isNaN(parseFloat(core.getInput(numberInput)))) {
|
||||
const errorMessage = `Option "${numberInput}" did not parse to a valid float`;
|
||||
core.setFailed(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
for (const numberInput of ['days-before-close', 'operations-per-run']) {
|
||||
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
||||
const errorMessage = `Option "${numberInput}" did not parse to a valid integer`;
|
||||
@ -2601,9 +2936,10 @@ function _getAndValidateArgs() {
|
||||
}
|
||||
return args;
|
||||
}
|
||||
function processOutput(staledIssues, closedIssues) {
|
||||
function processOutput(staledIssues, rottenIssues, closedIssues) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.setOutput('staled-issues-prs', JSON.stringify(staledIssues));
|
||||
core.setOutput('rotten-issues-prs', JSON.stringify(rottenIssues));
|
||||
core.setOutput('closed-issues-prs', JSON.stringify(closedIssues));
|
||||
});
|
||||
}
|
||||
|
||||
@ -20,9 +20,12 @@ describe('Issue', (): void => {
|
||||
daysBeforeClose: 0,
|
||||
daysBeforeIssueClose: 0,
|
||||
daysBeforeIssueStale: 0,
|
||||
daysBeforeIssueRotten: 0,
|
||||
daysBeforePrClose: 0,
|
||||
daysBeforePrStale: 0,
|
||||
daysBeforePrRotten: 0,
|
||||
daysBeforeStale: 0,
|
||||
daysBeforeRotten: 0,
|
||||
debugOnly: false,
|
||||
deleteBranch: false,
|
||||
exemptIssueLabels: '',
|
||||
@ -37,12 +40,19 @@ describe('Issue', (): void => {
|
||||
removeStaleWhenUpdated: false,
|
||||
removeIssueStaleWhenUpdated: undefined,
|
||||
removePrStaleWhenUpdated: undefined,
|
||||
removeRottenWhenUpdated: false,
|
||||
removeIssueRottenWhenUpdated: undefined,
|
||||
removePrRottenWhenUpdated: undefined,
|
||||
repoToken: '',
|
||||
staleIssueMessage: '',
|
||||
stalePrMessage: '',
|
||||
rottenIssueMessage: '',
|
||||
rottenPrMessage: '',
|
||||
startDate: undefined,
|
||||
stalePrLabel: 'dummy-stale-pr-label',
|
||||
staleIssueLabel: 'dummy-stale-issue-label',
|
||||
rottenPrLabel: 'dummy-rotten-pr-label',
|
||||
rottenIssueLabel: 'dummy-rotten-issue-label',
|
||||
exemptMilestones: '',
|
||||
exemptIssueMilestones: '',
|
||||
exemptPrMilestones: '',
|
||||
@ -59,6 +69,9 @@ describe('Issue', (): void => {
|
||||
labelsToRemoveWhenStale: '',
|
||||
labelsToRemoveWhenUnstale: '',
|
||||
labelsToAddWhenUnstale: '',
|
||||
labelsToRemoveWhenRotten: '',
|
||||
labelsToRemoveWhenUnrotten: '',
|
||||
labelsToAddWhenUnrotten: '',
|
||||
ignoreUpdates: false,
|
||||
ignoreIssueUpdates: undefined,
|
||||
ignorePrUpdates: undefined,
|
||||
|
||||
@ -21,7 +21,9 @@ export class Issue implements IIssue {
|
||||
readonly milestone?: IMilestone | null;
|
||||
readonly assignees: Assignee[];
|
||||
isStale: boolean;
|
||||
isRotten: boolean;
|
||||
markedStaleThisRun: boolean;
|
||||
markedRottenThisRun: boolean;
|
||||
operations = new Operations();
|
||||
private readonly _options: IIssuesProcessorOptions;
|
||||
|
||||
@ -42,7 +44,9 @@ export class Issue implements IIssue {
|
||||
this.milestone = issue.milestone;
|
||||
this.assignees = issue.assignees || [];
|
||||
this.isStale = isLabeled(this, this.staleLabel);
|
||||
this.isRotten = isLabeled(this, this.rottenLabel);
|
||||
this.markedStaleThisRun = false;
|
||||
this.markedRottenThisRun = false;
|
||||
}
|
||||
|
||||
get isPullRequest(): boolean {
|
||||
@ -52,6 +56,9 @@ export class Issue implements IIssue {
|
||||
get staleLabel(): string {
|
||||
return this._getStaleLabel();
|
||||
}
|
||||
get rottenLabel(): string {
|
||||
return this._getRottenLabel();
|
||||
}
|
||||
|
||||
get hasAssignees(): boolean {
|
||||
return this.assignees.length > 0;
|
||||
@ -62,6 +69,11 @@ export class Issue implements IIssue {
|
||||
? this._options.stalePrLabel
|
||||
: this._options.staleIssueLabel;
|
||||
}
|
||||
private _getRottenLabel(): string {
|
||||
return this.isPullRequest
|
||||
? this._options.rottenPrLabel
|
||||
: this._options.rottenIssueLabel;
|
||||
}
|
||||
}
|
||||
|
||||
function mapLabels(labels: (string | ILabel)[] | ILabel[]): ILabel[] {
|
||||
|
||||
@ -69,6 +69,7 @@ export class IssuesProcessor {
|
||||
readonly client: InstanceType<typeof GitHub>;
|
||||
readonly options: IIssuesProcessorOptions;
|
||||
readonly staleIssues: Issue[] = [];
|
||||
readonly rottenIssues: Issue[] = [];
|
||||
readonly closedIssues: Issue[] = [];
|
||||
readonly deletedBranchIssues: Issue[] = [];
|
||||
readonly removedLabelIssues: Issue[] = [];
|
||||
@ -141,6 +142,16 @@ export class IssuesProcessor {
|
||||
const labelsToRemoveWhenUnstale: string[] = wordsToList(
|
||||
this.options.labelsToRemoveWhenUnstale
|
||||
);
|
||||
const labelsToRemoveWhenRotten: string[] = wordsToList(
|
||||
this.options.labelsToRemoveWhenRotten
|
||||
);
|
||||
|
||||
const labelsToAddWhenUnrotten: string[] = wordsToList(
|
||||
this.options.labelsToAddWhenUnrotten
|
||||
);
|
||||
const labelsToRemoveWhenUnrotten: string[] = wordsToList(
|
||||
this.options.labelsToRemoveWhenUnrotten
|
||||
);
|
||||
|
||||
for (const issue of issues.values()) {
|
||||
// Stop the processing if no more operations remains
|
||||
@ -160,7 +171,10 @@ export class IssuesProcessor {
|
||||
issue,
|
||||
labelsToAddWhenUnstale,
|
||||
labelsToRemoveWhenUnstale,
|
||||
labelsToRemoveWhenStale
|
||||
labelsToRemoveWhenStale,
|
||||
labelsToAddWhenUnrotten,
|
||||
labelsToRemoveWhenUnrotten,
|
||||
labelsToRemoveWhenRotten
|
||||
);
|
||||
});
|
||||
this.state.addIssueToProcessed(issue);
|
||||
@ -200,7 +214,10 @@ export class IssuesProcessor {
|
||||
issue: Issue,
|
||||
labelsToAddWhenUnstale: Readonly<string>[],
|
||||
labelsToRemoveWhenUnstale: Readonly<string>[],
|
||||
labelsToRemoveWhenStale: Readonly<string>[]
|
||||
labelsToRemoveWhenStale: Readonly<string>[],
|
||||
labelsToAddWhenUnrotten: Readonly<string>[],
|
||||
labelsToRemoveWhenUnrotten: Readonly<string>[],
|
||||
labelsToRemoveWhenRotten: Readonly<string>[]
|
||||
): Promise<void> {
|
||||
this.statistics?.incrementProcessedItemsCount(issue);
|
||||
|
||||
@ -215,12 +232,21 @@ export class IssuesProcessor {
|
||||
const staleMessage: string = issue.isPullRequest
|
||||
? this.options.stalePrMessage
|
||||
: this.options.staleIssueMessage;
|
||||
const rottenMessage: string = issue.isPullRequest
|
||||
? this.options.rottenPrMessage
|
||||
: this.options.rottenIssueMessage;
|
||||
const closeMessage: string = issue.isPullRequest
|
||||
? this.options.closePrMessage
|
||||
: this.options.closeIssueMessage;
|
||||
const skipRottenMessage = issue.isPullRequest
|
||||
? this.options.rottenPrMessage.length === 0
|
||||
: this.options.rottenIssueMessage.length === 0;
|
||||
const staleLabel: string = issue.isPullRequest
|
||||
? this.options.stalePrLabel
|
||||
: this.options.staleIssueLabel;
|
||||
const rottenLabel: string = issue.isPullRequest
|
||||
? this.options.rottenPrLabel
|
||||
: this.options.rottenIssueLabel;
|
||||
const closeLabel: string = issue.isPullRequest
|
||||
? this.options.closePrLabel
|
||||
: this.options.closeIssueLabel;
|
||||
@ -342,10 +368,16 @@ export class IssuesProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the issue is stale, if not, check if it is rotten and then log the findings.
|
||||
if (issue.isStale) {
|
||||
issueLogger.info(`This $$type includes a stale label`);
|
||||
} else {
|
||||
issueLogger.info(`This $$type does not include a stale label`);
|
||||
if (issue.isRotten) {
|
||||
issueLogger.info(`This $$type includes a rotten label`);
|
||||
} else {
|
||||
issueLogger.info(`This $$type does not include a rotten label`);
|
||||
}
|
||||
}
|
||||
|
||||
const exemptLabels: string[] = wordsToList(
|
||||
@ -445,78 +477,92 @@ export class IssuesProcessor {
|
||||
return; // Don't process draft PR
|
||||
}
|
||||
|
||||
// Here we are looking into if the issue is stale or not, and then adding the label. This same code will also be used for the rotten label.
|
||||
// Determine if this issue needs to be marked stale first
|
||||
if (!issue.isStale) {
|
||||
issueLogger.info(`This $$type is not stale`);
|
||||
|
||||
const shouldIgnoreUpdates: boolean = new IgnoreUpdates(
|
||||
this.options,
|
||||
issue
|
||||
).shouldIgnoreUpdates();
|
||||
|
||||
// Should this issue be marked as stale?
|
||||
let shouldBeStale: boolean;
|
||||
|
||||
// Ignore the last update and only use the creation date
|
||||
if (shouldIgnoreUpdates) {
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(
|
||||
issue.created_at,
|
||||
daysBeforeStale
|
||||
if (issue.isRotten) {
|
||||
await this._processRottenIssue(
|
||||
issue,
|
||||
rottenLabel,
|
||||
rottenMessage,
|
||||
labelsToAddWhenUnrotten,
|
||||
labelsToRemoveWhenUnrotten,
|
||||
labelsToRemoveWhenRotten,
|
||||
closeMessage,
|
||||
closeLabel
|
||||
);
|
||||
}
|
||||
// Use the last update to check if we need to stale
|
||||
else {
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(
|
||||
issue.updated_at,
|
||||
daysBeforeStale
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldBeStale) {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(
|
||||
`This $$type should be stale based on the creation date the ${getHumanizedDate(
|
||||
new Date(issue.created_at)
|
||||
)} (${LoggerService.cyan(issue.created_at)})`
|
||||
);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should be stale based on the last update date the ${getHumanizedDate(
|
||||
new Date(issue.updated_at)
|
||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldMarkAsStale) {
|
||||
issueLogger.info(
|
||||
`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(
|
||||
this._getDaysBeforeStaleUsedOptionName(issue)
|
||||
)} (${LoggerService.cyan(daysBeforeStale)})`
|
||||
);
|
||||
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||
issue.isStale = true; // This issue is now considered stale
|
||||
issue.markedStaleThisRun = true;
|
||||
issueLogger.info(`This $$type is now stale`);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(
|
||||
this._getDaysBeforeStaleUsedOptionName(issue)
|
||||
)} (${LoggerService.cyan(daysBeforeStale)})`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const shouldIgnoreUpdates: boolean = new IgnoreUpdates(
|
||||
this.options,
|
||||
issue
|
||||
).shouldIgnoreUpdates();
|
||||
|
||||
// Should this issue be marked as stale?
|
||||
let shouldBeStale: boolean;
|
||||
|
||||
// Ignore the last update and only use the creation date
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(
|
||||
`This $$type should not be stale based on the creation date the ${getHumanizedDate(
|
||||
new Date(issue.created_at)
|
||||
)} (${LoggerService.cyan(issue.created_at)})`
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(
|
||||
issue.created_at,
|
||||
daysBeforeStale
|
||||
);
|
||||
}
|
||||
// Use the last update to check if we need to stale
|
||||
else {
|
||||
shouldBeStale = !IssuesProcessor._updatedSince(
|
||||
issue.updated_at,
|
||||
daysBeforeStale
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldBeStale) {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(
|
||||
`This $$type should be stale based on the creation date the ${getHumanizedDate(
|
||||
new Date(issue.created_at)
|
||||
)} (${LoggerService.cyan(issue.created_at)})`
|
||||
);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should be stale based on the last update date the ${getHumanizedDate(
|
||||
new Date(issue.updated_at)
|
||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldMarkAsStale) {
|
||||
issueLogger.info(
|
||||
`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(
|
||||
this._getDaysBeforeStaleUsedOptionName(issue)
|
||||
)} (${LoggerService.cyan(daysBeforeStale)})`
|
||||
);
|
||||
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||
issue.isStale = true; // This issue is now considered stale
|
||||
issue.markedStaleThisRun = true;
|
||||
issueLogger.info(`This $$type is now stale`);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(
|
||||
this._getDaysBeforeStaleUsedOptionName(issue)
|
||||
)} (${LoggerService.cyan(daysBeforeStale)})`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should not be stale based on the last update date the ${getHumanizedDate(
|
||||
new Date(issue.updated_at)
|
||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||
);
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(
|
||||
`This $$type should not be stale based on the creation date the ${getHumanizedDate(
|
||||
new Date(issue.created_at)
|
||||
)} (${LoggerService.cyan(issue.created_at)})`
|
||||
);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should not be stale based on the last update date the ${getHumanizedDate(
|
||||
new Date(issue.updated_at)
|
||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -528,11 +574,17 @@ export class IssuesProcessor {
|
||||
issue,
|
||||
staleLabel,
|
||||
staleMessage,
|
||||
rottenLabel,
|
||||
rottenMessage,
|
||||
closeLabel,
|
||||
closeMessage,
|
||||
labelsToAddWhenUnstale,
|
||||
labelsToRemoveWhenUnstale,
|
||||
labelsToRemoveWhenStale,
|
||||
closeMessage,
|
||||
closeLabel
|
||||
labelsToAddWhenUnrotten,
|
||||
labelsToRemoveWhenUnrotten,
|
||||
labelsToRemoveWhenRotten,
|
||||
skipRottenMessage
|
||||
);
|
||||
}
|
||||
|
||||
@ -650,17 +702,28 @@ export class IssuesProcessor {
|
||||
}
|
||||
|
||||
// handle all of the stale issue logic when we find a stale issue
|
||||
// This whole thing needs to be altered, to be calculated based on the days to rotten, rather than days to close or whatever
|
||||
private async _processStaleIssue(
|
||||
issue: Issue,
|
||||
staleLabel: string,
|
||||
staleMessage: string,
|
||||
rottenLabel: string,
|
||||
rottenMessage: string,
|
||||
closeLabel: string,
|
||||
closeMessage: string,
|
||||
labelsToAddWhenUnstale: Readonly<string>[],
|
||||
labelsToRemoveWhenUnstale: Readonly<string>[],
|
||||
labelsToRemoveWhenStale: Readonly<string>[],
|
||||
closeMessage?: string,
|
||||
closeLabel?: string
|
||||
labelsToAddWhenUnrotten: Readonly<string>[],
|
||||
labelsToRemoveWhenUnrotten: Readonly<string>[],
|
||||
labelsToRemoveWhenRotten: Readonly<string>[],
|
||||
skipMessage: boolean
|
||||
) {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
let issueHasClosed: boolean = false;
|
||||
|
||||
// We can get the label creation date from the getLableCreationDate function
|
||||
const markedStaleOn: string =
|
||||
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
|
||||
issueLogger.info(
|
||||
@ -678,12 +741,15 @@ export class IssuesProcessor {
|
||||
)}`
|
||||
);
|
||||
|
||||
const daysBeforeRotten: number = issue.isPullRequest
|
||||
? this._getDaysBeforePrRotten()
|
||||
: this._getDaysBeforeIssueRotten();
|
||||
|
||||
const daysBeforeClose: number = issue.isPullRequest
|
||||
? this._getDaysBeforePrClose()
|
||||
: this._getDaysBeforeIssueClose();
|
||||
|
||||
issueLogger.info(
|
||||
`Days before $$type close: ${LoggerService.cyan(daysBeforeClose)}`
|
||||
`Days before $$type rotten: ${LoggerService.cyan(daysBeforeRotten)}`
|
||||
);
|
||||
|
||||
const shouldRemoveStaleWhenUpdated: boolean =
|
||||
@ -703,6 +769,7 @@ export class IssuesProcessor {
|
||||
);
|
||||
}
|
||||
|
||||
// we will need to use a variation of this for the rotten state
|
||||
if (issue.markedStaleThisRun) {
|
||||
issueLogger.info(`marked stale this run, so don't check for updates`);
|
||||
await this._removeLabelsOnStatusTransition(
|
||||
@ -750,9 +817,254 @@ export class IssuesProcessor {
|
||||
return; // Nothing to do because it is no longer stale
|
||||
}
|
||||
|
||||
if (daysBeforeRotten < 0) {
|
||||
if (daysBeforeClose < 0) {
|
||||
issueLogger.info(
|
||||
`Stale $$type cannot be rotten or closed because days before rotten: ${daysBeforeRotten}, and days before close: ${daysBeforeClose}`
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`Closing issue without rottening it because days before $$type rotten: ${LoggerService.cyan(
|
||||
daysBeforeRotten
|
||||
)}`
|
||||
);
|
||||
|
||||
const issueHasUpdateInCloseWindow: boolean =
|
||||
IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose);
|
||||
issueLogger.info(
|
||||
`$$type has been updated in the last ${daysBeforeClose} days: ${LoggerService.cyan(
|
||||
issueHasUpdateInCloseWindow
|
||||
)}`
|
||||
);
|
||||
if (!issueHasUpdateInCloseWindow && !issueHasCommentsSinceStale) {
|
||||
issueLogger.info(
|
||||
`Closing $$type because it was last updated on: ${LoggerService.cyan(
|
||||
issue.updated_at
|
||||
)}`
|
||||
);
|
||||
await this._closeIssue(issue, closeMessage, closeLabel);
|
||||
|
||||
issueHasClosed = true;
|
||||
|
||||
if (this.options.deleteBranch && issue.pull_request) {
|
||||
issueLogger.info(
|
||||
`Deleting the branch since the option ${issueLogger.createOptionLink(
|
||||
Option.DeleteBranch
|
||||
)} is enabled`
|
||||
);
|
||||
await this._deleteBranch(issue);
|
||||
this.deletedBranchIssues.push(issue);
|
||||
}
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`Stale $$type is not old enough to close yet (hasComments? ${issueHasCommentsSinceStale}, hasUpdate? ${issueHasUpdateInCloseWindow})`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make a function for shouldMarkWhenRotten
|
||||
const shouldMarkAsRotten: boolean = shouldMarkWhenStale(daysBeforeRotten);
|
||||
|
||||
if (issueHasClosed) {
|
||||
issueLogger.info(
|
||||
`Issue $$type has been closed, no need to process it further.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!issue.isRotten) {
|
||||
issueLogger.info(`This $$type is not rotten`);
|
||||
|
||||
const shouldIgnoreUpdates: boolean = new IgnoreUpdates(
|
||||
this.options,
|
||||
issue
|
||||
).shouldIgnoreUpdates();
|
||||
|
||||
const shouldBeRotten: boolean = !IssuesProcessor._updatedSince(
|
||||
issue.updated_at,
|
||||
daysBeforeRotten
|
||||
);
|
||||
|
||||
if (shouldBeRotten) {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(
|
||||
`This $$type should be rotten based on the creation date the ${getHumanizedDate(
|
||||
new Date(issue.created_at)
|
||||
)} (${LoggerService.cyan(issue.created_at)})`
|
||||
);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should be rotten based on the last update date the ${getHumanizedDate(
|
||||
new Date(issue.updated_at)
|
||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldMarkAsRotten) {
|
||||
issueLogger.info(
|
||||
`This $$type should be marked as rotten based on the option ${issueLogger.createOptionLink(
|
||||
this._getDaysBeforeRottenUsedOptionName(issue)
|
||||
)} (${LoggerService.cyan(daysBeforeRotten)})`
|
||||
);
|
||||
// remove the stale label before marking the issue as rotten
|
||||
await this._removeStaleLabel(issue, staleLabel);
|
||||
|
||||
await this._markRotten(
|
||||
issue,
|
||||
rottenMessage,
|
||||
rottenLabel,
|
||||
skipMessage
|
||||
);
|
||||
issue.isRotten = true; // This issue is now considered rotten
|
||||
issue.markedRottenThisRun = true;
|
||||
issueLogger.info(`This $$type is now rotten`);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type should not be marked as rotten based on the option ${issueLogger.createOptionLink(
|
||||
this._getDaysBeforeStaleUsedOptionName(issue)
|
||||
)} (${LoggerService.cyan(daysBeforeRotten)})`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (shouldIgnoreUpdates) {
|
||||
issueLogger.info(
|
||||
`This $$type is not old enough to be rotten based on the creation date the ${getHumanizedDate(
|
||||
new Date(issue.created_at)
|
||||
)} (${LoggerService.cyan(issue.created_at)})`
|
||||
);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`This $$type is not old enough to be rotten based on the creation date the ${getHumanizedDate(
|
||||
new Date(issue.updated_at)
|
||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (issue.isRotten) {
|
||||
issueLogger.info(`This $$type is already rotten`);
|
||||
// process the rotten issues
|
||||
this._processRottenIssue(
|
||||
issue,
|
||||
rottenLabel,
|
||||
rottenMessage,
|
||||
labelsToAddWhenUnrotten,
|
||||
labelsToRemoveWhenUnrotten,
|
||||
labelsToRemoveWhenRotten,
|
||||
closeMessage,
|
||||
closeLabel
|
||||
);
|
||||
}
|
||||
}
|
||||
private async _processRottenIssue(
|
||||
issue: Issue,
|
||||
rottenLabel: string,
|
||||
rottenMessage: string,
|
||||
labelsToAddWhenUnrotten: Readonly<string>[],
|
||||
labelsToRemoveWhenUnrotten: Readonly<string>[],
|
||||
labelsToRemoveWhenRotten: Readonly<string>[],
|
||||
closeMessage?: string,
|
||||
closeLabel?: string
|
||||
) {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
// We can get the label creation date from the getLableCreationDate function
|
||||
const markedRottenOn: string =
|
||||
(await this.getLabelCreationDate(issue, rottenLabel)) || issue.updated_at;
|
||||
issueLogger.info(
|
||||
`$$type marked rotten on: ${LoggerService.cyan(markedRottenOn)}`
|
||||
);
|
||||
|
||||
const issueHasCommentsSinceRotten: boolean = await this._hasCommentsSince(
|
||||
issue,
|
||||
markedRottenOn,
|
||||
rottenMessage
|
||||
);
|
||||
issueLogger.info(
|
||||
`$$type has been commented on: ${LoggerService.cyan(
|
||||
issueHasCommentsSinceRotten
|
||||
)}`
|
||||
);
|
||||
|
||||
const daysBeforeClose: number = issue.isPullRequest
|
||||
? this._getDaysBeforePrClose()
|
||||
: this._getDaysBeforeIssueClose();
|
||||
|
||||
issueLogger.info(
|
||||
`Days before $$type close: ${LoggerService.cyan(daysBeforeClose)}`
|
||||
);
|
||||
|
||||
const shouldRemoveRottenWhenUpdated: boolean =
|
||||
this._shouldRemoveRottenWhenUpdated(issue);
|
||||
|
||||
issueLogger.info(
|
||||
`The option ${issueLogger.createOptionLink(
|
||||
this._getRemoveRottenWhenUpdatedUsedOptionName(issue)
|
||||
)} is: ${LoggerService.cyan(shouldRemoveRottenWhenUpdated)}`
|
||||
);
|
||||
|
||||
if (shouldRemoveRottenWhenUpdated) {
|
||||
issueLogger.info(`The rotten label should not be removed`);
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`The rotten label should be removed if all conditions met`
|
||||
);
|
||||
}
|
||||
|
||||
if (issue.markedRottenThisRun) {
|
||||
issueLogger.info(`marked rotten this run, so don't check for updates`);
|
||||
await this._removeLabelsOnStatusTransition(
|
||||
issue,
|
||||
labelsToRemoveWhenRotten,
|
||||
Option.LabelsToRemoveWhenRotten
|
||||
);
|
||||
}
|
||||
|
||||
// The issue.updated_at and markedRottenOn 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 issueHasUpdateSinceRotten = isDateMoreRecentThan(
|
||||
new Date(issue.updated_at),
|
||||
new Date(markedRottenOn),
|
||||
15
|
||||
);
|
||||
|
||||
issueLogger.info(
|
||||
`$$type has been updated since it was marked rotten: ${LoggerService.cyan(
|
||||
issueHasUpdateSinceRotten
|
||||
)}`
|
||||
);
|
||||
|
||||
// Should we un-rotten this issue?
|
||||
if (
|
||||
shouldRemoveRottenWhenUpdated &&
|
||||
(issueHasUpdateSinceRotten || issueHasCommentsSinceRotten) &&
|
||||
!issue.markedRottenThisRun
|
||||
) {
|
||||
issueLogger.info(
|
||||
`Remove the rotten label since the $$type has been updated and the workflow should remove the stale label when updated`
|
||||
);
|
||||
await this._removeRottenLabel(issue, rottenLabel);
|
||||
|
||||
// Are there labels to remove or add when an issue is no longer rotten?
|
||||
// This logic takes care of removing labels when unrotten
|
||||
await this._removeLabelsOnStatusTransition(
|
||||
issue,
|
||||
labelsToRemoveWhenUnrotten,
|
||||
Option.LabelsToRemoveWhenUnrotten
|
||||
);
|
||||
await this._addLabelsWhenUnrotten(issue, labelsToAddWhenUnrotten);
|
||||
|
||||
issueLogger.info(
|
||||
`Skipping the process since the $$type is now un-rotten`
|
||||
);
|
||||
|
||||
return; // Nothing to do because it is no longer rotten
|
||||
}
|
||||
|
||||
// Now start closing logic
|
||||
if (daysBeforeClose < 0) {
|
||||
return; // Nothing to do because we aren't closing stale issues
|
||||
return; // Nothing to do because we aren't closing rotten issues
|
||||
}
|
||||
|
||||
const issueHasUpdateInCloseWindow: boolean = IssuesProcessor._updatedSince(
|
||||
@ -765,7 +1077,7 @@ export class IssuesProcessor {
|
||||
)}`
|
||||
);
|
||||
|
||||
if (!issueHasCommentsSinceStale && !issueHasUpdateInCloseWindow) {
|
||||
if (!issueHasCommentsSinceRotten && !issueHasUpdateInCloseWindow) {
|
||||
issueLogger.info(
|
||||
`Closing $$type because it was last updated on: ${LoggerService.cyan(
|
||||
issue.updated_at
|
||||
@ -784,7 +1096,7 @@ export class IssuesProcessor {
|
||||
}
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`Stale $$type is not old enough to close yet (hasComments? ${issueHasCommentsSinceStale}, hasUpdate? ${issueHasUpdateInCloseWindow})`
|
||||
`Rotten $$type is not old enough to close yet (hasComments? ${issueHasCommentsSinceRotten}, hasUpdate? ${issueHasUpdateInCloseWindow})`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -876,6 +1188,57 @@ export class IssuesProcessor {
|
||||
issueLogger.error(`Error when adding a label: ${error.message}`);
|
||||
}
|
||||
}
|
||||
private async _markRotten(
|
||||
issue: Issue,
|
||||
rottenMessage: string,
|
||||
rottenLabel: string,
|
||||
skipMessage: boolean
|
||||
): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(`Marking this $$type as rotten`);
|
||||
this.rottenIssues.push(issue);
|
||||
|
||||
// if the issue is being marked rotten, the updated date should be changed to right now
|
||||
// so that close calculations work correctly
|
||||
const newUpdatedAtDate: Date = new Date();
|
||||
issue.updated_at = newUpdatedAtDate.toString();
|
||||
|
||||
if (!skipMessage) {
|
||||
try {
|
||||
this._consumeIssueOperation(issue);
|
||||
this.statistics?.incrementAddedItemsComment(issue);
|
||||
|
||||
if (!this.options.debugOnly) {
|
||||
await this.client.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: rottenMessage
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
issueLogger.error(`Error when creating a comment: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this._consumeIssueOperation(issue);
|
||||
this.statistics?.incrementAddedItemsLabel(issue);
|
||||
this.statistics?.incrementStaleItemsCount(issue);
|
||||
|
||||
if (!this.options.debugOnly) {
|
||||
await this.client.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [rottenLabel]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
issueLogger.error(`Error when adding a label: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Close an issue based on staleness
|
||||
private async _closeIssue(
|
||||
@ -885,7 +1248,7 @@ export class IssuesProcessor {
|
||||
): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(`Closing $$type for being stale`);
|
||||
issueLogger.info(`Closing $$type for being stale/rotten`);
|
||||
this.closedIssues.push(issue);
|
||||
|
||||
if (closeMessage) {
|
||||
@ -1056,6 +1419,17 @@ export class IssuesProcessor {
|
||||
? this.options.daysBeforeStale
|
||||
: this.options.daysBeforePrStale;
|
||||
}
|
||||
private _getDaysBeforeIssueRotten(): number {
|
||||
return isNaN(this.options.daysBeforeIssueRotten)
|
||||
? this.options.daysBeforeRotten
|
||||
: this.options.daysBeforeIssueRotten;
|
||||
}
|
||||
|
||||
private _getDaysBeforePrRotten(): number {
|
||||
return isNaN(this.options.daysBeforePrRotten)
|
||||
? this.options.daysBeforeRotten
|
||||
: this.options.daysBeforePrRotten;
|
||||
}
|
||||
|
||||
private _getDaysBeforeIssueClose(): number {
|
||||
return isNaN(this.options.daysBeforeIssueClose)
|
||||
@ -1116,6 +1490,21 @@ export class IssuesProcessor {
|
||||
|
||||
return this.options.removeStaleWhenUpdated;
|
||||
}
|
||||
private _shouldRemoveRottenWhenUpdated(issue: Issue): boolean {
|
||||
if (issue.isPullRequest) {
|
||||
if (isBoolean(this.options.removePrRottenWhenUpdated)) {
|
||||
return this.options.removePrRottenWhenUpdated;
|
||||
}
|
||||
|
||||
return this.options.removeRottenWhenUpdated;
|
||||
}
|
||||
|
||||
if (isBoolean(this.options.removeIssueRottenWhenUpdated)) {
|
||||
return this.options.removeIssueRottenWhenUpdated;
|
||||
}
|
||||
|
||||
return this.options.removeRottenWhenUpdated;
|
||||
}
|
||||
|
||||
private async _removeLabelsOnStatusTransition(
|
||||
issue: Issue,
|
||||
@ -1175,6 +1564,42 @@ export class IssuesProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private async _addLabelsWhenUnrotten(
|
||||
issue: Issue,
|
||||
labelsToAdd: Readonly<string>[]
|
||||
): Promise<void> {
|
||||
if (!labelsToAdd.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(
|
||||
`Adding all the labels specified via the ${this._logger.createOptionLink(
|
||||
Option.LabelsToAddWhenUnrotten
|
||||
)} option.`
|
||||
);
|
||||
|
||||
// TODO: this might need to be changed to a set to avoiod repetition
|
||||
this.addedLabelIssues.push(issue);
|
||||
|
||||
try {
|
||||
this._consumeIssueOperation(issue);
|
||||
this.statistics?.incrementAddedItemsLabel(issue);
|
||||
if (!this.options.debugOnly) {
|
||||
await this.client.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: labelsToAdd
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this._logger.error(
|
||||
`Error when adding labels after updated from rotten: ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
private async _removeStaleLabel(
|
||||
issue: Issue,
|
||||
staleLabel: Readonly<string>
|
||||
@ -1188,6 +1613,19 @@ export class IssuesProcessor {
|
||||
await this._removeLabel(issue, staleLabel);
|
||||
this.statistics?.incrementUndoStaleItemsCount(issue);
|
||||
}
|
||||
private async _removeRottenLabel(
|
||||
issue: Issue,
|
||||
rottenLabel: Readonly<string>
|
||||
): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(
|
||||
`The $$type is no longer rotten. Removing the rotten label...`
|
||||
);
|
||||
|
||||
await this._removeLabel(issue, rottenLabel);
|
||||
this.statistics?.incrementUndoRottenItemsCount(issue);
|
||||
}
|
||||
|
||||
private async _removeCloseLabel(
|
||||
issue: Issue,
|
||||
@ -1266,6 +1704,32 @@ export class IssuesProcessor {
|
||||
: Option.DaysBeforePrStale;
|
||||
}
|
||||
|
||||
private _getDaysBeforeRottenUsedOptionName(
|
||||
issue: Readonly<Issue>
|
||||
):
|
||||
| Option.DaysBeforeRotten
|
||||
| Option.DaysBeforeIssueRotten
|
||||
| Option.DaysBeforePrRotten {
|
||||
return issue.isPullRequest
|
||||
? this._getDaysBeforePrRottenUsedOptionName()
|
||||
: this._getDaysBeforeIssueRottenUsedOptionName();
|
||||
}
|
||||
|
||||
private _getDaysBeforeIssueRottenUsedOptionName():
|
||||
| Option.DaysBeforeRotten
|
||||
| Option.DaysBeforeIssueRotten {
|
||||
return isNaN(this.options.daysBeforeIssueRotten)
|
||||
? Option.DaysBeforeRotten
|
||||
: Option.DaysBeforeIssueRotten;
|
||||
}
|
||||
|
||||
private _getDaysBeforePrRottenUsedOptionName():
|
||||
| Option.DaysBeforeRotten
|
||||
| Option.DaysBeforePrRotten {
|
||||
return isNaN(this.options.daysBeforePrRotten)
|
||||
? Option.DaysBeforeRotten
|
||||
: Option.DaysBeforePrRotten;
|
||||
}
|
||||
private _getRemoveStaleWhenUpdatedUsedOptionName(
|
||||
issue: Readonly<Issue>
|
||||
):
|
||||
@ -1286,4 +1750,24 @@ export class IssuesProcessor {
|
||||
|
||||
return Option.RemoveStaleWhenUpdated;
|
||||
}
|
||||
private _getRemoveRottenWhenUpdatedUsedOptionName(
|
||||
issue: Readonly<Issue>
|
||||
):
|
||||
| Option.RemovePrRottenWhenUpdated
|
||||
| Option.RemoveRottenWhenUpdated
|
||||
| Option.RemoveIssueRottenWhenUpdated {
|
||||
if (issue.isPullRequest) {
|
||||
if (isBoolean(this.options.removePrRottenWhenUpdated)) {
|
||||
return Option.RemovePrRottenWhenUpdated;
|
||||
}
|
||||
|
||||
return Option.RemoveRottenWhenUpdated;
|
||||
}
|
||||
|
||||
if (isBoolean(this.options.removeIssueRottenWhenUpdated)) {
|
||||
return Option.RemoveIssueRottenWhenUpdated;
|
||||
}
|
||||
|
||||
return Option.RemoveRottenWhenUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,10 @@ export class Statistics {
|
||||
stalePullRequestsCount = 0;
|
||||
undoStaleIssuesCount = 0;
|
||||
undoStalePullRequestsCount = 0;
|
||||
rottenIssuesCount = 0;
|
||||
rottenPullRequestsCount = 0;
|
||||
undoRottenIssuesCount = 0;
|
||||
undoRottenPullRequestsCount = 0;
|
||||
operationsCount = 0;
|
||||
closedIssuesCount = 0;
|
||||
closedPullRequestsCount = 0;
|
||||
@ -65,6 +69,17 @@ export class Statistics {
|
||||
return this._incrementUndoStaleIssuesCount(increment);
|
||||
}
|
||||
|
||||
incrementUndoRottenItemsCount(
|
||||
issue: Readonly<Issue>,
|
||||
increment: Readonly<number> = 1
|
||||
): Statistics {
|
||||
if (issue.isPullRequest) {
|
||||
return this._incrementUndoRottenPullRequestsCount(increment);
|
||||
}
|
||||
|
||||
return this._incrementUndoRottenIssuesCount(increment);
|
||||
}
|
||||
|
||||
setOperationsCount(operationsCount: Readonly<number>): Statistics {
|
||||
this.operationsCount = operationsCount;
|
||||
|
||||
@ -222,6 +237,21 @@ export class Statistics {
|
||||
return this;
|
||||
}
|
||||
|
||||
private _incrementUndoRottenPullRequestsCount(
|
||||
increment: Readonly<number> = 1
|
||||
): Statistics {
|
||||
this.undoRottenPullRequestsCount += increment;
|
||||
|
||||
return this;
|
||||
}
|
||||
private _incrementUndoRottenIssuesCount(
|
||||
increment: Readonly<number> = 1
|
||||
): Statistics {
|
||||
this.undoRottenIssuesCount += increment;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private _incrementUndoStalePullRequestsCount(
|
||||
increment: Readonly<number> = 1
|
||||
): Statistics {
|
||||
|
||||
@ -2,18 +2,25 @@ export enum Option {
|
||||
RepoToken = 'repo-token',
|
||||
StaleIssueMessage = 'stale-issue-message',
|
||||
StalePrMessage = 'stale-pr-message',
|
||||
RottenIssueMessage = 'rotten-issue-message',
|
||||
RottenPrMessage = 'rotten-pr-message',
|
||||
CloseIssueMessage = 'close-issue-message',
|
||||
ClosePrMessage = 'close-pr-message',
|
||||
DaysBeforeStale = 'days-before-stale',
|
||||
DaysBeforeIssueStale = 'days-before-issue-stale',
|
||||
DaysBeforePrStale = 'days-before-pr-stale',
|
||||
DaysBeforeRotten = 'days-before-rotten',
|
||||
DaysBeforeIssueRotten = 'days-before-issue-rotten',
|
||||
DaysBeforePrRotten = 'days-before-pr-rotten',
|
||||
DaysBeforeClose = 'days-before-close',
|
||||
DaysBeforeIssueClose = 'days-before-issue-close',
|
||||
DaysBeforePrClose = 'days-before-pr-close',
|
||||
StaleIssueLabel = 'stale-issue-label',
|
||||
RottenIssueLabel = 'rotten-issue-label',
|
||||
CloseIssueLabel = 'close-issue-label',
|
||||
ExemptIssueLabels = 'exempt-issue-labels',
|
||||
StalePrLabel = 'stale-pr-label',
|
||||
RottenPrLabel = 'rotten-pr-label',
|
||||
ClosePrLabel = 'close-pr-label',
|
||||
ExemptPrLabels = 'exempt-pr-labels',
|
||||
OnlyLabels = 'only-labels',
|
||||
@ -24,6 +31,9 @@ export enum Option {
|
||||
RemoveStaleWhenUpdated = 'remove-stale-when-updated',
|
||||
RemoveIssueStaleWhenUpdated = 'remove-issue-stale-when-updated',
|
||||
RemovePrStaleWhenUpdated = 'remove-pr-stale-when-updated',
|
||||
RemoveRottenWhenUpdated = 'remove-rotten-when-updated',
|
||||
RemoveIssueRottenWhenUpdated = 'remove-issue-rotten-when-updated',
|
||||
RemovePrRottenWhenUpdated = 'remove-pr-rotten-when-updated',
|
||||
DebugOnly = 'debug-only',
|
||||
Ascending = 'ascending',
|
||||
DeleteBranch = 'delete-branch',
|
||||
@ -44,6 +54,9 @@ export enum Option {
|
||||
LabelsToRemoveWhenStale = 'labels-to-remove-when-stale',
|
||||
LabelsToRemoveWhenUnstale = 'labels-to-remove-when-unstale',
|
||||
LabelsToAddWhenUnstale = 'labels-to-add-when-unstale',
|
||||
LabelsToRemoveWhenRotten = 'labels-to-remove-when-rotten',
|
||||
LabelsToRemoveWhenUnrotten = 'labels-to-remove-when-unrotten',
|
||||
LabelsToAddWhenUnrotten = 'labels-to-add-when-unrotten',
|
||||
IgnoreUpdates = 'ignore-updates',
|
||||
IgnoreIssueUpdates = 'ignore-issue-updates',
|
||||
IgnorePrUpdates = 'ignore-pr-updates',
|
||||
|
||||
@ -4,18 +4,25 @@ export interface IIssuesProcessorOptions {
|
||||
repoToken: string;
|
||||
staleIssueMessage: string;
|
||||
stalePrMessage: string;
|
||||
rottenIssueMessage: string;
|
||||
rottenPrMessage: string;
|
||||
closeIssueMessage: string;
|
||||
closePrMessage: string;
|
||||
daysBeforeStale: number;
|
||||
daysBeforeIssueStale: number; // Could be NaN
|
||||
daysBeforePrStale: number; // Could be NaN
|
||||
daysBeforeRotten: number;
|
||||
daysBeforeIssueRotten: number; // Could be NaN
|
||||
daysBeforePrRotten: number; // Could be NaN
|
||||
daysBeforeClose: number;
|
||||
daysBeforeIssueClose: number; // Could be NaN
|
||||
daysBeforePrClose: number; // Could be NaN
|
||||
staleIssueLabel: string;
|
||||
rottenIssueLabel: string;
|
||||
closeIssueLabel: string;
|
||||
exemptIssueLabels: string;
|
||||
stalePrLabel: string;
|
||||
rottenPrLabel: string;
|
||||
closePrLabel: string;
|
||||
exemptPrLabels: string;
|
||||
onlyLabels: string;
|
||||
@ -28,6 +35,9 @@ export interface IIssuesProcessorOptions {
|
||||
removeStaleWhenUpdated: boolean;
|
||||
removeIssueStaleWhenUpdated: boolean | undefined;
|
||||
removePrStaleWhenUpdated: boolean | undefined;
|
||||
removeRottenWhenUpdated: boolean;
|
||||
removeIssueRottenWhenUpdated: boolean | undefined;
|
||||
removePrRottenWhenUpdated: boolean | undefined;
|
||||
debugOnly: boolean;
|
||||
ascending: boolean;
|
||||
deleteBranch: boolean;
|
||||
@ -48,6 +58,9 @@ export interface IIssuesProcessorOptions {
|
||||
labelsToRemoveWhenStale: string;
|
||||
labelsToRemoveWhenUnstale: string;
|
||||
labelsToAddWhenUnstale: string;
|
||||
labelsToRemoveWhenRotten: string;
|
||||
labelsToRemoveWhenUnrotten: string;
|
||||
labelsToAddWhenUnrotten: string;
|
||||
ignoreUpdates: boolean;
|
||||
ignoreIssueUpdates: boolean | undefined;
|
||||
ignorePrUpdates: boolean | undefined;
|
||||
|
||||
33
src/main.ts
33
src/main.ts
@ -46,6 +46,7 @@ async function _run(): Promise<void> {
|
||||
|
||||
await processOutput(
|
||||
issueProcessor.staleIssues,
|
||||
issueProcessor.rottenIssues,
|
||||
issueProcessor.closedIssues
|
||||
);
|
||||
} catch (error) {
|
||||
@ -59,22 +60,33 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||
repoToken: core.getInput('repo-token'),
|
||||
staleIssueMessage: core.getInput('stale-issue-message'),
|
||||
stalePrMessage: core.getInput('stale-pr-message'),
|
||||
rottenIssueMessage: core.getInput('rotten-issue-message'),
|
||||
rottenPrMessage: core.getInput('rotten-pr-message'),
|
||||
closeIssueMessage: core.getInput('close-issue-message'),
|
||||
closePrMessage: core.getInput('close-pr-message'),
|
||||
daysBeforeStale: parseFloat(
|
||||
core.getInput('days-before-stale', {required: true})
|
||||
),
|
||||
daysBeforeRotten: parseFloat(
|
||||
core.getInput('days-before-rotten', {required: true})
|
||||
),
|
||||
daysBeforeIssueStale: parseFloat(core.getInput('days-before-issue-stale')),
|
||||
daysBeforePrStale: parseFloat(core.getInput('days-before-pr-stale')),
|
||||
daysBeforeIssueRotten: parseFloat(
|
||||
core.getInput('days-before-issue-rotten')
|
||||
),
|
||||
daysBeforePrRotten: parseFloat(core.getInput('days-before-pr-rotten')),
|
||||
daysBeforeClose: parseInt(
|
||||
core.getInput('days-before-close', {required: true})
|
||||
),
|
||||
daysBeforeIssueClose: parseInt(core.getInput('days-before-issue-close')),
|
||||
daysBeforePrClose: parseInt(core.getInput('days-before-pr-close')),
|
||||
staleIssueLabel: core.getInput('stale-issue-label', {required: true}),
|
||||
rottenIssueLabel: core.getInput('rotten-issue-label', {required: true}),
|
||||
closeIssueLabel: core.getInput('close-issue-label'),
|
||||
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
||||
stalePrLabel: core.getInput('stale-pr-label', {required: true}),
|
||||
rottenPrLabel: core.getInput('rotten-pr-label', {required: true}),
|
||||
closePrLabel: core.getInput('close-pr-label'),
|
||||
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
||||
onlyLabels: core.getInput('only-labels'),
|
||||
@ -95,6 +107,15 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||
removePrStaleWhenUpdated: _toOptionalBoolean(
|
||||
'remove-pr-stale-when-updated'
|
||||
),
|
||||
removeRottenWhenUpdated: !(
|
||||
core.getInput('remove-rotten-when-updated') === 'false'
|
||||
),
|
||||
removeIssueRottenWhenUpdated: _toOptionalBoolean(
|
||||
'remove-issue-rotten-when-updated'
|
||||
),
|
||||
removePrRottenWhenUpdated: _toOptionalBoolean(
|
||||
'remove-pr-rotten-when-updated'
|
||||
),
|
||||
debugOnly: core.getInput('debug-only') === 'true',
|
||||
ascending: core.getInput('ascending') === 'true',
|
||||
deleteBranch: core.getInput('delete-branch') === 'true',
|
||||
@ -118,6 +139,9 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||
labelsToRemoveWhenStale: core.getInput('labels-to-remove-when-stale'),
|
||||
labelsToRemoveWhenUnstale: core.getInput('labels-to-remove-when-unstale'),
|
||||
labelsToAddWhenUnstale: core.getInput('labels-to-add-when-unstale'),
|
||||
labelsToRemoveWhenRotten: core.getInput('labels-to-remove-when-rotten'),
|
||||
labelsToRemoveWhenUnrotten: core.getInput('labels-to-remove-when-unrotten'),
|
||||
labelsToAddWhenUnrotten: core.getInput('labels-to-add-when-unrotten'),
|
||||
ignoreUpdates: core.getInput('ignore-updates') === 'true',
|
||||
ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'),
|
||||
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
|
||||
@ -133,6 +157,13 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
for (const numberInput of ['days-before-rotten']) {
|
||||
if (isNaN(parseFloat(core.getInput(numberInput)))) {
|
||||
const errorMessage = `Option "${numberInput}" did not parse to a valid float`;
|
||||
core.setFailed(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
for (const numberInput of ['days-before-close', 'operations-per-run']) {
|
||||
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
||||
@ -167,9 +198,11 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||
|
||||
async function processOutput(
|
||||
staledIssues: Issue[],
|
||||
rottenIssues: Issue[],
|
||||
closedIssues: Issue[]
|
||||
): Promise<void> {
|
||||
core.setOutput('staled-issues-prs', JSON.stringify(staledIssues));
|
||||
core.setOutput('rotten-issues-prs', JSON.stringify(rottenIssues));
|
||||
core.setOutput('closed-issues-prs', JSON.stringify(closedIssues));
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user