mirror of
https://github.com/actions/stale.git
synced 2025-12-26 18:28:18 +00:00
Compare commits
85 Commits
v3.0.12
...
issue-comm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc8ff56517 | ||
|
|
ab559aee86 | ||
|
|
ffc3c749f4 | ||
|
|
93db9b6708 | ||
|
|
3b3c3f03cd | ||
|
|
70f07a7b62 | ||
|
|
6a2a52084e | ||
|
|
bea117ba11 | ||
|
|
c181784783 | ||
|
|
93cc018477 | ||
|
|
b80d40901f | ||
|
|
10e1968c4f | ||
|
|
ba1c02f61a | ||
|
|
419a53bc05 | ||
|
|
63ae8ac024 | ||
|
|
8f5f223d0c | ||
|
|
aac59820db | ||
|
|
4109d00d07 | ||
|
|
b3213c1ffa | ||
|
|
12d218f917 | ||
|
|
e1732283c7 | ||
|
|
8439944051 | ||
|
|
0e95ddbecb | ||
|
|
836169b81a | ||
|
|
ec96ff65b0 | ||
|
|
996798eb71 | ||
|
|
6a0398d581 | ||
|
|
7499904a44 | ||
|
|
a8c9fbca3b | ||
|
|
8df2fb36e6 | ||
|
|
d4d9b2a583 | ||
|
|
aee97aa05d | ||
|
|
d6e4b48a0d | ||
|
|
6a493760cf | ||
|
|
07f3f88b6d | ||
|
|
e2a65edb45 | ||
|
|
076138c5c7 | ||
|
|
bf7548e250 | ||
|
|
e96f31f877 | ||
|
|
9d6f46564a | ||
|
|
d21d307fd8 | ||
|
|
7164109781 | ||
|
|
079c368275 | ||
|
|
86561461b9 | ||
|
|
342b612d3c | ||
|
|
d09524cd2c | ||
|
|
f71123a6f7 | ||
|
|
1b9f13b607 | ||
|
|
f8321eb62b | ||
|
|
6a04021499 | ||
|
|
7e83de7bef | ||
|
|
a9eccc216e | ||
|
|
0fa41d1841 | ||
|
|
f698371c0d | ||
|
|
7f340a46f3 | ||
|
|
3f95874437 | ||
|
|
e6b77bc964 | ||
|
|
552e4c60f0 | ||
|
|
b12dccced8 | ||
|
|
7de5f1946d | ||
|
|
546a4e9fd6 | ||
|
|
b5b956deb3 | ||
|
|
6674e9130f | ||
|
|
73dbf86108 | ||
|
|
a0b4b61711 | ||
|
|
0f78ebc7dc | ||
|
|
b70b09d43a | ||
|
|
aca4d264ff | ||
|
|
44c3cb508b | ||
|
|
39de730f99 | ||
|
|
ddc7648635 | ||
|
|
107018c400 | ||
|
|
87c2b794b9 | ||
|
|
b7c7b1c5c3 | ||
|
|
08900ff5d1 | ||
|
|
6ea8297c81 | ||
|
|
23d5d28543 | ||
|
|
2c5799741a | ||
|
|
324009e5d0 | ||
|
|
9b82e8c1ef | ||
|
|
af40726159 | ||
|
|
658139a1f1 | ||
|
|
707ba4d103 | ||
|
|
62b357a603 | ||
|
|
f75b3c75ce |
115
.eslintrc.json
115
.eslintrc.json
@@ -1,52 +1,65 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["jest", "@typescript-eslint"],
|
"plugins": ["jest", "@typescript-eslint"],
|
||||||
"extends": ["plugin:github/recommended"],
|
"extends": ["plugin:github/recommended"],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 9,
|
"ecmaVersion": 9,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"eslint-comments/no-use": "off",
|
"eslint-comments/no-use": "off",
|
||||||
"import/no-namespace": "off",
|
"import/no-namespace": "off",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
"@typescript-eslint/explicit-member-accessibility": [
|
||||||
"@typescript-eslint/no-require-imports": "error",
|
"error",
|
||||||
"@typescript-eslint/array-type": "error",
|
{
|
||||||
"@typescript-eslint/await-thenable": "error",
|
"accessibility": "no-public"
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
}
|
||||||
"camelcase": "off",
|
],
|
||||||
"@typescript-eslint/consistent-type-assertions": "error",
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
"@typescript-eslint/array-type": "error",
|
||||||
"@typescript-eslint/no-array-constructor": "error",
|
"@typescript-eslint/await-thenable": "error",
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
"@typescript-eslint/ban-ts-comment": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"camelcase": "off",
|
||||||
"@typescript-eslint/no-extraneous-class": "error",
|
"@typescript-eslint/consistent-type-assertions": "error",
|
||||||
"@typescript-eslint/no-for-in-array": "error",
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
"@typescript-eslint/no-inferrable-types": "error",
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
"@typescript-eslint/no-misused-new": "error",
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
"@typescript-eslint/no-namespace": "error",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
"@typescript-eslint/no-for-in-array": "error",
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
"@typescript-eslint/no-inferrable-types": "error",
|
||||||
"@typescript-eslint/no-useless-constructor": "error",
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
"@typescript-eslint/no-var-requires": "error",
|
"@typescript-eslint/no-namespace": "error",
|
||||||
"@typescript-eslint/prefer-for-of": "warn",
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
"@typescript-eslint/prefer-function-type": "warn",
|
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||||
"@typescript-eslint/prefer-includes": "error",
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
"@typescript-eslint/promise-function-async": "error",
|
"@typescript-eslint/no-var-requires": "error",
|
||||||
"@typescript-eslint/require-array-sort-compare": "error",
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
"@typescript-eslint/restrict-plus-operands": "error",
|
"@typescript-eslint/prefer-function-type": "warn",
|
||||||
"semi": "off",
|
"@typescript-eslint/prefer-includes": "error",
|
||||||
"@typescript-eslint/type-annotation-spacing": "error",
|
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||||
"@typescript-eslint/unbound-method": "off"
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
},
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
"env": {
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
"node": true,
|
"semi": "off",
|
||||||
"es6": true,
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
"jest/globals": true
|
"@typescript-eslint/unbound-method": "off",
|
||||||
}
|
"no-shadow": "off",
|
||||||
}
|
"@typescript-eslint/no-shadow": "error",
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es6": true,
|
||||||
|
"jest/globals": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
31
.github/workflows/codeql.yml
vendored
31
.github/workflows/codeql.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: "Code scanning"
|
name: 'Code scanning'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -8,29 +8,16 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CodeQL-Build:
|
CodeQL-Build:
|
||||||
|
|
||||||
# CodeQL runs on ubuntu-latest and windows-latest
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
# We must fetch at least the immediate parents so that if this is
|
|
||||||
# a pull request then we can checkout the head.
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
- name: Initialize CodeQL
|
||||||
# the head of the pull request instead of the merge commit.
|
uses: github/codeql-action/init@v1
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v1
|
||||||
|
|||||||
16
.github/workflows/stale.yml
vendored
16
.github/workflows/stale.yml
vendored
@@ -1,16 +1,16 @@
|
|||||||
name: "Stale issue handler"
|
name: 'Stale issue handler'
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: '0 0 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@main
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
days-before-stale: 30
|
||||||
days-before-stale: 30
|
days-before-close: 5
|
||||||
days-before-close: 5
|
exempt-issue-labels: 'blocked,must,should,keep'
|
||||||
|
|||||||
23
.github/workflows/test.yml
vendored
23
.github/workflows/test.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: "Build"
|
name: 'Build'
|
||||||
on: # rebuild any PRs and main branch changes
|
on: # rebuild any PRs and main branch changes
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
@@ -10,17 +10,16 @@ jobs:
|
|||||||
build: # make sure build/ci work properly
|
build: # make sure build/ci work properly
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: |
|
- run: |
|
||||||
npm install
|
npm install
|
||||||
npm run all
|
npm run all
|
||||||
test: # make sure the action works on a clean machine without building
|
test: # make sure the action works on a clean machine without building
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: ./
|
- uses: ./
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
stale-issue-message: 'This issue is stale'
|
||||||
stale-issue-message: 'This issue is stale'
|
stale-pr-message: 'This PR is stale'
|
||||||
stale-pr-message: 'This PR is stale'
|
debug-only: true
|
||||||
debug-only: true
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
lib/
|
lib/
|
||||||
__tests__/runner/*
|
__tests__/runner/*
|
||||||
|
.idea
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ allowed:
|
|||||||
- unlicense
|
- unlicense
|
||||||
|
|
||||||
reviewed:
|
reviewed:
|
||||||
npm:
|
npm:
|
||||||
|
|||||||
7
.prettierignore
Normal file
7
.prettierignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.idea
|
||||||
|
.licenses
|
||||||
|
.vscode
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 80,
|
"printWidth": 80,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"bracketSpacing": false,
|
"bracketSpacing": false,
|
||||||
"arrowParens": "avoid",
|
"arrowParens": "avoid",
|
||||||
"parser": "typescript"
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
|
|||||||
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -9,7 +9,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"-i"
|
"-i"
|
||||||
],
|
],
|
||||||
"preLaunchTask": "tsc: build - tsconfig.json",
|
"preLaunchTask": "tsc: build - tsconfig.app.json",
|
||||||
"internalConsoleOptions": "openOnSessionStart",
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"outFiles": [
|
"outFiles": [
|
||||||
@@ -17,4 +17,4 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* @actions/actions-runtime
|
||||||
45
CONTRIBUTING.md
Normal file
45
CONTRIBUTING.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
### Building and testing
|
||||||
|
|
||||||
|
Install the dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the typescript and package it for distribution.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm run build && npm run pack
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the tests :heavy_check_mark:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the tests and display only the first failing tests :heavy_check_mark:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm test:only-errors
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the tests with the watch mode :heavy_check_mark:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm test:watch
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the linter and fix (almost) every issue for you :heavy_check_mark:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm lint:all:fix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Before creating a PR
|
||||||
|
|
||||||
|
Build, lint, package and test everything.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm all
|
||||||
|
```
|
||||||
303
README.md
303
README.md
@@ -2,87 +2,296 @@
|
|||||||
|
|
||||||
Warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
Warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
||||||
|
|
||||||
### Building and testing
|
### Arguments
|
||||||
|
|
||||||
Install the dependencies
|
Every argument is optional.
|
||||||
```bash
|
|
||||||
$ npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Build the typescript and package it for distribution
|
| Input | Description |
|
||||||
```bash
|
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
$ npm run build && npm run pack
|
| `repo-token` | PAT(Personal Access Token) for authorizing repository.<br>_Defaults to **${{ github.token }}**_. |
|
||||||
```
|
| `days-before-stale` | Idle number of days before marking an issue/PR as stale.<br>_Defaults to **60**_. |
|
||||||
|
| `days-before-issue-stale` | Idle number of days before marking an issue as stale.<br>_Override `days-before-stale`_. |
|
||||||
Run the tests :heavy_check_mark:
|
| `days-before-pr-stale` | Idle number of days before marking an PR as stale.<br>_Override `days-before-stale`_. |
|
||||||
```bash
|
| `days-before-close` | Idle number of days before closing an stale issue/PR.<br>_Defaults to **7**_. |
|
||||||
$ npm test
|
| `days-before-issue-close` | Idle number of days before closing an stale issue.<br>_Override `days-before-close`_. |
|
||||||
```
|
| `days-before-pr-close` | Idle number of days before closing an stale PR.<br>_Override `days-before-close`_. |
|
||||||
|
| `stale-issue-message` | Message to post on the stale issue. |
|
||||||
|
| `stale-pr-message` | Message to post on the stale PR. |
|
||||||
|
| `close-issue-message` | Message to post on the stale issue while closing it. |
|
||||||
|
| `close-pr-message` | Message to post on the stale PR while closing it. |
|
||||||
|
| `stale-issue-label` | Label to apply on the stale issue.<br>_Defaults to **Stale**_. |
|
||||||
|
| `close-issue-label` | Label to apply on closing issue (automatically removed if no longer closed nor locked). |
|
||||||
|
| `stale-pr-label` | Label to apply on the stale PR.<br>_Defaults to **Stale**_. |
|
||||||
|
| `close-pr-label` | Label to apply on the closing PR (automatically removed if no longer closed nor locked). |
|
||||||
|
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. |
|
||||||
|
| `exempt-pr-labels` | Labels on the PR exempted from being marked as stale. |
|
||||||
|
| `only-labels` | Only issues and PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered"). |
|
||||||
|
| `only-issue-labels` | Only issues with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered").<br>_Override `only-labels`_. |
|
||||||
|
| `only-pr-labels` | Only PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered").<br>_Override `only-labels`_. |
|
||||||
|
| `any-of-labels` | Only issues and PRs with ANY of these labels are checked. Separate multiple labels with commas (eg. "incomplete,waiting-feedback"). |
|
||||||
|
| `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related).<br>_Defaults to **30**_. |
|
||||||
|
| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments.<br>_Defaults to **true**_. |
|
||||||
|
| `debug-only` | Dry-run on action.<br>_Defaults to **false**_. |
|
||||||
|
| `ascending` | Order to get issues/PR (true is ascending, false is descending).<br>_Defaults to **false**_. |
|
||||||
|
| `skip-stale-issue-message` | Skip adding stale message on stale issue.<br>_Defaults to **false**_. |
|
||||||
|
| `skip-stale-pr-message` | Skip adding stale message on stale PR.<br>_Defaults to **false**_. |
|
||||||
|
| `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). |
|
||||||
|
| `delete-branch` | Delete the git branch after closing a stale pull request.<br>_Defaults to **false**_. |
|
||||||
|
| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. |
|
||||||
|
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale.<br>_Override `exempt-milestones`_. |
|
||||||
|
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale.<br>_Override `exempt-milestones`_. |
|
||||||
|
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale.<br>_Priority over `exempt-milestones` rules_. |
|
||||||
|
| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale.<br>_Override `exempt-all-milestones`_. |
|
||||||
|
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale.<br>_Override `exempt-all-milestones`_. |
|
||||||
|
| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. |
|
||||||
|
| `exempt-issue-assignees` | Assignees on an issue exempted from being marked as stale.<br>_Override `exempt-assignees`_. |
|
||||||
|
| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale.<br>_Override `exempt-assignees`_. |
|
||||||
|
| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale.<br>_Priority over `exempt-assignees` rules_. |
|
||||||
|
| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale.<br>_Override `exempt-all-assignees`_. |
|
||||||
|
| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale.<br>_Override `exempt-all-assignees`_. |
|
||||||
|
| `enable-statistics` | Display some statistics at the end of the logs regarding the stale workflow (only when the logs are enabled).<br>_Defaults to **true**_. |
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
See [action.yml](./action.yml) For comprehensive list of options.
|
See also [action.yml](./action.yml) for a comprehensive list of all the options.
|
||||||
|
|
||||||
Basic:
|
Basic:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: "Close stale issues"
|
name: 'Close stale issues and PRs'
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v3
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
stale-issue-message: 'Message to comment on stale issues. If none provided, will not mark issues stale'
|
||||||
stale-issue-message: 'Message to comment on stale issues. If none provided, will not mark issues stale'
|
stale-pr-message: 'Message to comment on stale PRs. If none provided, will not mark PRs stale'
|
||||||
stale-pr-message: 'Message to comment on stale PRs. If none provided, will not mark PRs stale'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure stale timeouts:
|
Configure stale timeouts:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: "Close stale issues"
|
name: 'Close stale issues and PRs'
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v3
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
days-before-stale: 30
|
||||||
days-before-stale: 30
|
days-before-close: 5
|
||||||
days-before-close: 5
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure labels:
|
Configure different stale timeouts but never close a PR:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: "Close stale issues"
|
name: 'Close stale issues and PR'
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v3
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: 'Stale issue message'
|
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||||
stale-pr-message: 'Stale pull request message'
|
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||||
stale-issue-label: 'no-issue-activity'
|
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
|
||||||
exempt-issue-labels: 'awaiting-approval,work-in-progress'
|
days-before-stale: 30
|
||||||
stale-pr-label: 'no-pr-activity'
|
days-before-close: 5
|
||||||
exempt-pr-labels: 'awaiting-approval,work-in-progress'
|
days-before-pr-close: -1
|
||||||
only-labels: 'awaiting-feedback,awaiting-answers'
|
```
|
||||||
|
|
||||||
|
Configure different stale timeouts:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||||
|
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||||
|
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
|
||||||
|
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
|
||||||
|
days-before-issue-stale: 30
|
||||||
|
days-before-pr-stale: 45
|
||||||
|
days-before-issue-close: 5
|
||||||
|
days-before-pr-close: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure labels:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
stale-issue-message: 'Stale issue message'
|
||||||
|
stale-pr-message: 'Stale pull request message'
|
||||||
|
stale-issue-label: 'no-issue-activity'
|
||||||
|
exempt-issue-labels: 'awaiting-approval,work-in-progress'
|
||||||
|
stale-pr-label: 'no-pr-activity'
|
||||||
|
exempt-pr-labels: 'awaiting-approval,work-in-progress'
|
||||||
|
only-labels: 'awaiting-feedback,awaiting-answers'
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the stale action to only stale issue/PR created after the 18th april 2020:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
start-date: '2020-18-04T00:00:00Z' // ISO 8601 or RFC 2822
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid stale for specific milestones:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
exempt-issue-milestones: 'future,alpha,beta'
|
||||||
|
exempt-pr-milestones: 'bugfix,improvement'
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid stale for all PR with milestones:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
exempt-all-pr-milestones: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid stale for specific labels:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
any-of-labels: 'needs-more-info,needs-demo'
|
||||||
|
# You can opt for 'only-labels' instead if your usecase requires all labels
|
||||||
|
# to be present in the issue/PR
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid stale for specific assignees:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
exempt-issue-assignees: 'marco,polo'
|
||||||
|
exempt-pr-assignees: 'marco'
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid stale for all PR with assignees:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
exempt-all-pr-assignees: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
|
|
||||||
To see debug output from this action, you must set the secret `ACTIONS_STEP_DEBUG` to `true` in your repository. You can run this action in debug only mode (no actions will be taken on your issues) by passing `debug-only` `true` as an argument to the action.
|
**Logs:**
|
||||||
|
To see the debug output from this action, you must set the secret `ACTIONS_STEP_DEBUG` to `true` in your repository.
|
||||||
|
There is a lot of logs so this can be very helpful!
|
||||||
|
|
||||||
|
**Statistics:**
|
||||||
|
If the logs are enabled, you can also enable the statistics log which will be visible at the end of the logs once all issues were processed.
|
||||||
|
This is very helpful to have a quick understanding of the whole stale workflow.
|
||||||
|
Set `enable-statistics` to `true` in your workflow configuration file.
|
||||||
|
|
||||||
|
**Dry-run:**
|
||||||
|
You can run this action in debug only mode (no actions will be taken on your issues and pull requests) by passing `debug-only` to `true` as an argument to the action.
|
||||||
|
|
||||||
|
**More operations:**
|
||||||
|
You can increase the maximum number of operations per run by passing `operations-per-run` to `1000` for example which will help you to handle more operations in a single stale workflow run.
|
||||||
|
If the `debug-only` option is enabled, this is very helpful because the workflow will (almost) never reach the GitHub API rate, and you will be able to deep-dive into the logs.
|
||||||
|
|
||||||
|
**Job frequency:**
|
||||||
|
You could change the cron job frequency in the stale workflow to run the stale workflow more often.
|
||||||
|
Usually this is not very helpful though.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
You wish to contribute?
|
||||||
|
Check out the [contributing](CONTRIBUTING.md) file before helping us.
|
||||||
|
|||||||
107
__tests__/any-of-labels.spec.ts
Normal file
107
__tests__/any-of-labels.spec.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import {Issue} from '../src/classes/issue';
|
||||||
|
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
|
||||||
|
import {IssuesProcessorMock} from './classes/issues-processor-mock';
|
||||||
|
import {DefaultProcessorOptions} from './constants/default-processor-options';
|
||||||
|
import {generateIssue} from './functions/generate-issue';
|
||||||
|
|
||||||
|
describe('any-of-labels option', () => {
|
||||||
|
test('should do nothing when not set', async () => {
|
||||||
|
const sut = new IssuesProcessorBuilder()
|
||||||
|
.emptyAnyOfLabels()
|
||||||
|
.issues([{labels: [{name: 'some-label'}]}])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
await sut.processIssues();
|
||||||
|
|
||||||
|
expect(sut.staleIssues).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should skip it when none of the issue labels match', async () => {
|
||||||
|
const sut = new IssuesProcessorBuilder()
|
||||||
|
.anyOfLabels('skip-this-issue,and-this-one')
|
||||||
|
.issues([{labels: [{name: 'some-label'}, {name: 'some-other-label'}]}])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
await sut.processIssues();
|
||||||
|
|
||||||
|
expect(sut.staleIssues).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should skip it when the issue has no labels', async () => {
|
||||||
|
const sut = new IssuesProcessorBuilder()
|
||||||
|
.anyOfLabels('skip-this-issue,and-this-one')
|
||||||
|
.issues([{labels: []}])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
await sut.processIssues();
|
||||||
|
|
||||||
|
expect(sut.staleIssues).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should process it when one of the issue labels match', async () => {
|
||||||
|
const sut = new IssuesProcessorBuilder()
|
||||||
|
.anyOfLabels('skip-this-issue,and-this-one')
|
||||||
|
.issues([{labels: [{name: 'some-label'}, {name: 'skip-this-issue'}]}])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
await sut.processIssues();
|
||||||
|
|
||||||
|
expect(sut.staleIssues).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should process it when all the issue labels match', async () => {
|
||||||
|
const sut = new IssuesProcessorBuilder()
|
||||||
|
.anyOfLabels('skip-this-issue,and-this-one')
|
||||||
|
.issues([{labels: [{name: 'and-this-one'}, {name: 'skip-this-issue'}]}])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
await sut.processIssues();
|
||||||
|
|
||||||
|
expect(sut.staleIssues).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class IssuesProcessorBuilder {
|
||||||
|
private _options: IIssuesProcessorOptions;
|
||||||
|
private _issues: Issue[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._options = {...DefaultProcessorOptions};
|
||||||
|
this._issues = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
anyOfLabels(labels: string): IssuesProcessorBuilder {
|
||||||
|
this._options.anyOfLabels = labels;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyAnyOfLabels(): IssuesProcessorBuilder {
|
||||||
|
return this.anyOfLabels('');
|
||||||
|
}
|
||||||
|
|
||||||
|
issues(issues: Partial<Issue>[]): IssuesProcessorBuilder {
|
||||||
|
this._issues = issues.map(
|
||||||
|
(issue, index): Issue =>
|
||||||
|
generateIssue(
|
||||||
|
this._options,
|
||||||
|
index,
|
||||||
|
issue.title || 'Issue title',
|
||||||
|
issue.updated_at || '2000-01-01T00:00:00Z', // we only care about stale/expired issues here
|
||||||
|
issue.created_at || '2000-01-01T00:00:00Z',
|
||||||
|
issue.isPullRequest || false,
|
||||||
|
issue.labels ? issue.labels.map(label => label.name) : []
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): IssuesProcessorMock {
|
||||||
|
return new IssuesProcessorMock(
|
||||||
|
this._options,
|
||||||
|
async () => 'abot',
|
||||||
|
async p => (p === 1 ? this._issues : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
413
__tests__/assignees.spec.ts
Normal file
413
__tests__/assignees.spec.ts
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
import {Issue} from '../src/classes/issue';
|
||||||
|
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
|
||||||
|
import {IssuesProcessorMock} from './classes/issues-processor-mock';
|
||||||
|
import {DefaultProcessorOptions} from './constants/default-processor-options';
|
||||||
|
import {generateIssue} from './functions/generate-issue';
|
||||||
|
|
||||||
|
interface ITestData {
|
||||||
|
id: number;
|
||||||
|
isPullRequest: boolean;
|
||||||
|
assignees: string[];
|
||||||
|
exemptAllAssignees: boolean;
|
||||||
|
exemptAllIssueAssignees: boolean | undefined;
|
||||||
|
exemptAllPrAssignees: boolean | undefined;
|
||||||
|
exemptAssignees: string;
|
||||||
|
exemptIssueAssignees: string;
|
||||||
|
exemptPrAssignees: string;
|
||||||
|
shouldStale: boolean;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('assignees options', (): void => {
|
||||||
|
let opts: IIssuesProcessorOptions;
|
||||||
|
let testIssueList: Issue[];
|
||||||
|
let processor: IssuesProcessorMock;
|
||||||
|
|
||||||
|
const setTestIssueList = (
|
||||||
|
isPullRequest: boolean,
|
||||||
|
assignees: string[],
|
||||||
|
id: number
|
||||||
|
) => {
|
||||||
|
testIssueList = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
id,
|
||||||
|
'My first issue',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
isPullRequest,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
assignees
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const setProcessor = () => {
|
||||||
|
processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
async () => 'abot',
|
||||||
|
async p => (p === 1 ? testIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
opts = {...DefaultProcessorOptions};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each`
|
||||||
|
id | isPullRequest | assignees | exemptAllAssignees | exemptAllIssueAssignees | exemptAllPrAssignees | exemptAssignees | exemptIssueAssignees | exemptPrAssignees | shouldStale | description
|
||||||
|
${100} | ${false} | ${[]} | ${false} | ${undefined} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee'}
|
||||||
|
${101} | ${false} | ${[]} | ${true} | ${undefined} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and only exemptAllAssignees is enabled'}
|
||||||
|
${102} | ${false} | ${[]} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and only exemptAllIssueAssignees is enabled'}
|
||||||
|
${103} | ${false} | ${[]} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and only exemptAllPrAssignees is enabled'}
|
||||||
|
${104} | ${false} | ${[]} | ${true} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${105} | ${false} | ${[]} | ${true} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${106} | ${false} | ${[]} | ${true} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${107} | ${false} | ${[]} | ${true} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${108} | ${false} | ${[]} | ${false} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${109} | ${false} | ${[]} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue does not have an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${200} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${201} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${202} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${''} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and only exemptAllAssignees is enabled'}
|
||||||
|
${203} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and only exemptAllIssueAssignees is enabled'}
|
||||||
|
${204} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and only exemptAllPrAssignees is enabled'}
|
||||||
|
${205} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${206} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${207} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${208} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${209} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${210} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${211} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${212} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${213} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${300} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${301} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${302} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${303} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${304} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${305} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${306} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${307} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${308} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${309} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${310} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${311} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${312} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${313} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${400} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${401} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${402} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${403} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${404} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${405} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${406} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${407} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${408} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${409} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${410} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${411} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${412} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${413} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${500} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${501} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${502} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${503} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${504} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${505} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${506} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${507} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${508} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${509} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${510} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${511} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${512} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${513} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${600} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${601} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${602} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${603} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${604} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${605} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${606} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${607} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${608} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${609} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${610} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${611} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${612} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${613} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${700} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${701} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${702} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${703} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${704} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${705} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${706} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${707} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${708} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${709} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${710} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${711} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${712} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${713} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${800} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${801} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${802} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${803} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${804} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${805} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${806} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${807} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${808} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${809} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${810} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${811} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${812} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${813} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'assignee'} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${900} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${901} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${902} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${903} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${904} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${905} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${906} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${907} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${908} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${909} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${910} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${911} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${912} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${913} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'bad'} | ${''} | ${true} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1000} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1001} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1002} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1003} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1004} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1005} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1006} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1007} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1008} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1009} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1010} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1011} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1012} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1013} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1100} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1101} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1102} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1103} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1104} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1105} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1106} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1107} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1108} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1109} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1110} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1111} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1112} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1113} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${1200} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1201} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1202} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1203} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1204} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1205} | ${false} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1206} | ${false} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1207} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1208} | ${false} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1209} | ${false} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1210} | ${false} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1211} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1212} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1213} | ${false} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the issue has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${1300} | ${true} | ${[]} | ${false} | ${undefined} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee'}
|
||||||
|
${1301} | ${true} | ${[]} | ${true} | ${undefined} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and only exemptAllAssignees is enabled'}
|
||||||
|
${1302} | ${true} | ${[]} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and only exemptAllIssueAssignees is enabled'}
|
||||||
|
${1303} | ${true} | ${[]} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and only exemptAllPrAssignees is enabled'}
|
||||||
|
${1304} | ${true} | ${[]} | ${true} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${1305} | ${true} | ${[]} | ${true} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${1306} | ${true} | ${[]} | ${true} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${1307} | ${true} | ${[]} | ${true} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${1308} | ${true} | ${[]} | ${false} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${1309} | ${true} | ${[]} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request does not have an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${1400} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${1401} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${1402} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${''} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and only exemptAllAssignees is enabled'}
|
||||||
|
${1403} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and only exemptAllIssueAssignees is enabled'}
|
||||||
|
${1404} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and only exemptAllPrAssignees is enabled'}
|
||||||
|
${1405} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${1406} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${1407} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${1408} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${1409} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is disabled'}
|
||||||
|
${1410} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled and exemptAllIssueAssignees is enabled'}
|
||||||
|
${1411} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${1412} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${''} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is enabled'}
|
||||||
|
${1413} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${''} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled and exemptAllPrAssignees is disabled'}
|
||||||
|
${1500} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${1501} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${1502} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${1503} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${1504} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${1505} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${1506} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${1507} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${1508} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${1509} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${1510} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees has a different assignee'}
|
||||||
|
${1511} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${1513} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has a different assignee'}
|
||||||
|
${1600} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${1601} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1602} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1603} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1604} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1605} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${1606} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1607} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${1608} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1609} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${1610} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1611} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${1612} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees has the same assignee'}
|
||||||
|
${1613} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees has the same assignee'}
|
||||||
|
${1701} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1702} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1703} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1704} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1705} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1706} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1707} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1708} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1709} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1710} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1711} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'bad'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptIssueAssignees has a different assignee'}
|
||||||
|
${1800} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1801} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1802} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1803} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1804} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1805} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${'assignee'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1806} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1807} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1808} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${'assignee'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1809} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1810} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${'assignee'} | ${''} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${1900} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1901} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1902} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1903} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1904} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1905} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1906} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1907} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1908} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1909} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1910} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${1911} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled and exemptAssignees and exemptPrAssignees has a different assignee'}
|
||||||
|
${2000} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2001} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2002} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2003} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2004} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2005} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2006} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2007} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2008} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2009} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2010} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'bad'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has a different assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2100} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2101} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2102} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2103} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2104} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2105} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2106} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2107} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2108} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2109} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2110} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'bad'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has a different assignee'}
|
||||||
|
${2200} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2201} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2202} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2203} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2204} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2205} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2206} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2207} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2208} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2209} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2210} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2311} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2312} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2313} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${'assignee'} | ${''} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptIssueAssignees has the same assignee'}
|
||||||
|
${2300} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2301} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2302} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2303} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2304} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2305} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2306} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2307} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2308} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2309} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2310} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'bad'} | ${true} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2311} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'bad'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has a different assignee'}
|
||||||
|
${2400} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2401} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2402} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2403} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2404} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2405} | ${true} | ${['assignee']} | ${true} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2406} | ${true} | ${['assignee']} | ${true} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2407} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2408} | ${true} | ${['assignee']} | ${true} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is enabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2409} | ${true} | ${['assignee']} | ${false} | ${false} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2410} | ${true} | ${['assignee']} | ${false} | ${true} | ${undefined} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllIssueAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2411} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2412} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${true} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is enabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
${2413} | ${true} | ${['assignee']} | ${false} | ${undefined} | ${false} | ${'assignee'} | ${''} | ${'assignee'} | ${false} | ${'when the pull request has an assignee and exemptAllAssignees is disabled, exemptAllPrAssignees is disabled, exemptAssignees has the same assignee and exemptPrAssignees has the same assignee'}
|
||||||
|
`(
|
||||||
|
'$description',
|
||||||
|
({
|
||||||
|
id,
|
||||||
|
isPullRequest,
|
||||||
|
assignees,
|
||||||
|
exemptAllAssignees,
|
||||||
|
exemptAllIssueAssignees,
|
||||||
|
exemptAllPrAssignees,
|
||||||
|
exemptAssignees,
|
||||||
|
exemptIssueAssignees,
|
||||||
|
exemptPrAssignees,
|
||||||
|
shouldStale
|
||||||
|
}: ITestData): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
opts.exemptAllAssignees = exemptAllAssignees;
|
||||||
|
opts.exemptAllIssueAssignees = exemptAllIssueAssignees;
|
||||||
|
opts.exemptAllPrAssignees = exemptAllPrAssignees;
|
||||||
|
opts.exemptAssignees = exemptAssignees;
|
||||||
|
opts.exemptIssueAssignees = exemptIssueAssignees;
|
||||||
|
opts.exemptPrAssignees = exemptPrAssignees;
|
||||||
|
setTestIssueList(isPullRequest, assignees, id);
|
||||||
|
setProcessor();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should${
|
||||||
|
shouldStale ? '' : ' not'
|
||||||
|
} be marked as stale`, async () => {
|
||||||
|
expect.assertions(3);
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.staleIssues).toHaveLength(shouldStale ? 1 : 0);
|
||||||
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
|
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
38
__tests__/classes/issues-processor-mock.ts
Normal file
38
__tests__/classes/issues-processor-mock.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {Issue} from '../../src/classes/issue';
|
||||||
|
import {IssuesProcessor} from '../../src/classes/issues-processor';
|
||||||
|
import {IComment} from '../../src/interfaces/comment';
|
||||||
|
import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-options';
|
||||||
|
|
||||||
|
export class IssuesProcessorMock extends IssuesProcessor {
|
||||||
|
constructor(
|
||||||
|
options: IIssuesProcessorOptions,
|
||||||
|
getActor?: () => Promise<string>,
|
||||||
|
getIssues?: (page: number) => Promise<Issue[]>,
|
||||||
|
listIssueComments?: (
|
||||||
|
issueNumber: number,
|
||||||
|
sinceDate: string
|
||||||
|
) => Promise<IComment[]>,
|
||||||
|
getLabelCreationDate?: (
|
||||||
|
issue: Issue,
|
||||||
|
label: string
|
||||||
|
) => Promise<string | undefined>
|
||||||
|
) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
if (getActor) {
|
||||||
|
this.getActor = getActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getIssues) {
|
||||||
|
this.getIssues = getIssues;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listIssueComments) {
|
||||||
|
this.listIssueComments = listIssueComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getLabelCreationDate) {
|
||||||
|
this.getLabelCreationDate = getLabelCreationDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
__tests__/constants/default-processor-options.ts
Normal file
46
__tests__/constants/default-processor-options.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-options';
|
||||||
|
|
||||||
|
export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
||||||
|
repoToken: 'none',
|
||||||
|
staleIssueMessage: 'This issue is stale',
|
||||||
|
stalePrMessage: 'This PR is stale',
|
||||||
|
closeIssueMessage: 'This issue is being closed',
|
||||||
|
closePrMessage: 'This PR is being closed',
|
||||||
|
daysBeforeStale: 1,
|
||||||
|
daysBeforeIssueStale: NaN,
|
||||||
|
daysBeforePrStale: NaN,
|
||||||
|
daysBeforeClose: 30,
|
||||||
|
daysBeforeIssueClose: NaN,
|
||||||
|
daysBeforePrClose: NaN,
|
||||||
|
staleIssueLabel: 'Stale',
|
||||||
|
closeIssueLabel: '',
|
||||||
|
exemptIssueLabels: '',
|
||||||
|
stalePrLabel: 'Stale',
|
||||||
|
closePrLabel: '',
|
||||||
|
exemptPrLabels: '',
|
||||||
|
onlyLabels: '',
|
||||||
|
onlyIssueLabels: '',
|
||||||
|
onlyPrLabels: '',
|
||||||
|
anyOfLabels: '',
|
||||||
|
operationsPerRun: 100,
|
||||||
|
debugOnly: true,
|
||||||
|
removeStaleWhenUpdated: false,
|
||||||
|
ascending: false,
|
||||||
|
skipStaleIssueMessage: false,
|
||||||
|
skipStalePrMessage: false,
|
||||||
|
deleteBranch: false,
|
||||||
|
startDate: '',
|
||||||
|
exemptMilestones: '',
|
||||||
|
exemptIssueMilestones: '',
|
||||||
|
exemptPrMilestones: '',
|
||||||
|
exemptAllMilestones: false,
|
||||||
|
exemptAllIssueMilestones: undefined,
|
||||||
|
exemptAllPrMilestones: undefined,
|
||||||
|
exemptAssignees: '',
|
||||||
|
exemptIssueAssignees: '',
|
||||||
|
exemptPrAssignees: '',
|
||||||
|
exemptAllAssignees: false,
|
||||||
|
exemptAllIssueAssignees: undefined,
|
||||||
|
exemptAllPrAssignees: undefined,
|
||||||
|
enableStatistics: false
|
||||||
|
});
|
||||||
19
__tests__/functions/generate-iissue.ts
Normal file
19
__tests__/functions/generate-iissue.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {IIssue} from '../../src/interfaces/issue';
|
||||||
|
|
||||||
|
export function generateIIssue(
|
||||||
|
partialIssue?: Readonly<Partial<IIssue>>
|
||||||
|
): IIssue {
|
||||||
|
return {
|
||||||
|
milestone: undefined,
|
||||||
|
assignees: [],
|
||||||
|
labels: [],
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
number: Math.round(Math.random() * 5000),
|
||||||
|
pull_request: null,
|
||||||
|
title: 'dummy-title',
|
||||||
|
locked: false,
|
||||||
|
state: 'dummy-state',
|
||||||
|
...partialIssue
|
||||||
|
};
|
||||||
|
}
|
||||||
43
__tests__/functions/generate-issue.ts
Normal file
43
__tests__/functions/generate-issue.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import {Issue} from '../../src/classes/issue';
|
||||||
|
import {IAssignee} from '../../src/interfaces/assignee';
|
||||||
|
import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-options';
|
||||||
|
import {IsoDateString} from '../../src/types/iso-date-string';
|
||||||
|
|
||||||
|
export function generateIssue(
|
||||||
|
options: IIssuesProcessorOptions,
|
||||||
|
id: number,
|
||||||
|
title: string,
|
||||||
|
updatedAt: IsoDateString,
|
||||||
|
createdAt: IsoDateString = updatedAt,
|
||||||
|
isPullRequest = false,
|
||||||
|
labels: string[] = [],
|
||||||
|
isClosed = false,
|
||||||
|
isLocked = false,
|
||||||
|
milestone: string | undefined = undefined,
|
||||||
|
assignees: string[] = []
|
||||||
|
): Issue {
|
||||||
|
return new Issue(options, {
|
||||||
|
number: id,
|
||||||
|
labels: labels.map(l => {
|
||||||
|
return {name: l};
|
||||||
|
}),
|
||||||
|
title,
|
||||||
|
created_at: createdAt,
|
||||||
|
updated_at: updatedAt,
|
||||||
|
pull_request: isPullRequest ? {} : null,
|
||||||
|
state: isClosed ? 'closed' : 'open',
|
||||||
|
locked: isLocked,
|
||||||
|
milestone: milestone
|
||||||
|
? {
|
||||||
|
title: milestone
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
assignees: assignees.map(
|
||||||
|
(assignee: Readonly<string>): IAssignee => {
|
||||||
|
return {
|
||||||
|
login: assignee
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
2290
__tests__/main.spec.ts
Normal file
2290
__tests__/main.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,897 +0,0 @@
|
|||||||
import * as core from '@actions/core';
|
|
||||||
import * as github from '@actions/github';
|
|
||||||
import {Octokit} from '@octokit/rest';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IssueProcessor,
|
|
||||||
Issue,
|
|
||||||
Label,
|
|
||||||
IssueProcessorOptions
|
|
||||||
} from '../src/IssueProcessor';
|
|
||||||
|
|
||||||
function generateIssue(
|
|
||||||
id: number,
|
|
||||||
title: string,
|
|
||||||
updatedAt: string,
|
|
||||||
isPullRequest: boolean = false,
|
|
||||||
labels: string[] = [],
|
|
||||||
isClosed: boolean = false,
|
|
||||||
isLocked: boolean = false
|
|
||||||
): Issue {
|
|
||||||
return {
|
|
||||||
number: id,
|
|
||||||
labels: labels.map(l => {
|
|
||||||
return {name: l};
|
|
||||||
}),
|
|
||||||
title: title,
|
|
||||||
updated_at: updatedAt,
|
|
||||||
pull_request: isPullRequest ? {} : null,
|
|
||||||
state: isClosed ? 'closed' : 'open',
|
|
||||||
locked: isLocked
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const DefaultProcessorOptions: IssueProcessorOptions = Object.freeze({
|
|
||||||
repoToken: 'none',
|
|
||||||
staleIssueMessage: 'This issue is stale',
|
|
||||||
stalePrMessage: 'This PR is stale',
|
|
||||||
closeIssueMessage: 'This issue is being closed',
|
|
||||||
closePrMessage: 'This PR is being closed',
|
|
||||||
daysBeforeStale: 1,
|
|
||||||
daysBeforeClose: 30,
|
|
||||||
staleIssueLabel: 'Stale',
|
|
||||||
closeIssueLabel: '',
|
|
||||||
exemptIssueLabels: '',
|
|
||||||
stalePrLabel: 'Stale',
|
|
||||||
closePrLabel: '',
|
|
||||||
exemptPrLabels: '',
|
|
||||||
onlyLabels: '',
|
|
||||||
operationsPerRun: 100,
|
|
||||||
debugOnly: true,
|
|
||||||
removeStaleWhenUpdated: false,
|
|
||||||
ascending: false,
|
|
||||||
skipStaleIssueMessage: false,
|
|
||||||
skipStalePrMessage: false
|
|
||||||
});
|
|
||||||
|
|
||||||
test('empty issue list results in 1 operation', async () => {
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async () => [],
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
const operationsLeft = await processor.processIssues(1);
|
|
||||||
|
|
||||||
// processing an empty issue list should result in 1 operation
|
|
||||||
expect(operationsLeft).toEqual(99);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing an issue with no label will make it stale and close it, if it is old enough only if days-before-close is set to 0', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeClose = 0;
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing an issue with no label will make it stale and not close it if days-before-close is set to > 0', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeClose = 15;
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing an issue with no label will not make it stale if days-before-stale is set to -1', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
...DefaultProcessorOptions,
|
|
||||||
staleIssueMessage: '',
|
|
||||||
daysBeforeStale: -1
|
|
||||||
};
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing an issue with no label will make it stale but not close it', async () => {
|
|
||||||
// issue should be from 2 days ago so it will be
|
|
||||||
// stale but not close-able, based on default settings
|
|
||||||
let issueDate = new Date();
|
|
||||||
issueDate.setDate(issueDate.getDate() - 2);
|
|
||||||
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'An issue with no label', issueDate.toDateString())
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing a stale issue will close it', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A stale issue that should be closed',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
false,
|
|
||||||
['Stale']
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing a stale PR will close it', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A stale PR that should be closed',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
true,
|
|
||||||
['Stale']
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing a stale issue will close it even if configured not to mark as stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z', false, [
|
|
||||||
'Stale'
|
|
||||||
])
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
...DefaultProcessorOptions,
|
|
||||||
daysBeforeStale: -1,
|
|
||||||
staleIssueMessage: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processing a stale PR will close it even if configured not to mark as stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z', true, [
|
|
||||||
'Stale'
|
|
||||||
])
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
...DefaultProcessorOptions,
|
|
||||||
daysBeforeStale: -1,
|
|
||||||
stalePrMessage: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('closed issues will not be marked stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A closed issue that will not be marked',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
false,
|
|
||||||
[],
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => []
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale closed issues will not be closed', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A stale closed issue',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
false,
|
|
||||||
['Stale'],
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('closed prs will not be marked stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A closed PR that will not be marked',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
true,
|
|
||||||
[],
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale closed prs will not be closed', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A stale closed PR that will not be closed again',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
true,
|
|
||||||
['Stale'],
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('locked issues will not be marked stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A locked issue that will not be stale',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
false,
|
|
||||||
[],
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(DefaultProcessorOptions, async p =>
|
|
||||||
p == 1 ? TestIssueList : []
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale locked issues will not be closed', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A stale locked issue that will not be closed',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
false,
|
|
||||||
['Stale'],
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('locked prs will not be marked stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A locked PR that will not be marked stale',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
true,
|
|
||||||
[],
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(DefaultProcessorOptions, async p =>
|
|
||||||
p == 1 ? TestIssueList : []
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale locked prs will not be closed', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'A stale locked PR that will not be closed',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
true,
|
|
||||||
['Stale'],
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
DefaultProcessorOptions,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('exempt issue labels will not be marked stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'My first issue', '2020-01-01T17:00:00Z', false, [
|
|
||||||
'Exempt'
|
|
||||||
])
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.exemptIssueLabels = 'Exempt';
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('exempt issue labels will not be marked stale (multi issue label with spaces)', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'My first issue', '2020-01-01T17:00:00Z', false, ['Cool'])
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.exemptIssueLabels = 'Exempt, Cool, None';
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('exempt issue labels will not be marked stale (multi issue label)', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'My first issue', '2020-01-01T17:00:00Z', false, ['Cool'])
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.exemptIssueLabels = 'Exempt,Cool,None';
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('exempt pr labels will not be marked stale', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'My first issue', '2020-01-01T17:00:00Z', false, ['Cool']),
|
|
||||||
generateIssue(2, 'My first PR', '2020-01-01T17:00:00Z', true, ['Cool']),
|
|
||||||
generateIssue(3, 'Another issue', '2020-01-01T17:00:00Z', false)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.exemptIssueLabels = 'Cool';
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(2); // PR should get processed even though it has an exempt **issue** label
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale issues should not be closed if days is set to -1', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(1, 'My first issue', '2020-01-01T17:00:00Z', false, [
|
|
||||||
'Stale'
|
|
||||||
]),
|
|
||||||
generateIssue(2, 'My first PR', '2020-01-01T17:00:00Z', true, ['Stale']),
|
|
||||||
generateIssue(3, 'Another issue', '2020-01-01T17:00:00Z', false, ['Stale'])
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeClose = -1;
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale label should be removed if a comment was added to a stale issue', async () => {
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should un-stale',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
false,
|
|
||||||
['Stale']
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.removeStaleWhenUpdated = true;
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [{user: {login: 'notme', type: 'User'}}], // return a fake comment to indicate there was an update
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale label should not be removed if a comment was added by the bot (and the issue should be closed)', async () => {
|
|
||||||
github.context.actor = 'abot';
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should stay stale',
|
|
||||||
'2020-01-01T17:00:00Z',
|
|
||||||
false,
|
|
||||||
['Stale']
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.removeStaleWhenUpdated = true;
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [{user: {login: 'abot', type: 'User'}}], // return a fake comment to indicate there was an update by the bot
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale issues should not be closed until after the closed number of days', async () => {
|
|
||||||
let lastUpdate = new Date();
|
|
||||||
lastUpdate.setDate(lastUpdate.getDate() - 5);
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should be marked stale but not closed',
|
|
||||||
lastUpdate.toString(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
|
||||||
opts.daysBeforeClose = 1; // closes after 6 days
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale issues should be closed if the closed nubmer of days (additive) is also passed', async () => {
|
|
||||||
let lastUpdate = new Date();
|
|
||||||
lastUpdate.setDate(lastUpdate.getDate() - 7);
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should be stale and closed',
|
|
||||||
lastUpdate.toString(),
|
|
||||||
false,
|
|
||||||
['Stale']
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
|
||||||
opts.daysBeforeClose = 1; // closes after 6 days
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stale issues should not be closed until after the closed number of days (long)', async () => {
|
|
||||||
let lastUpdate = new Date();
|
|
||||||
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should be marked stale but not closed',
|
|
||||||
lastUpdate.toString(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
|
||||||
opts.daysBeforeClose = 20; // closes after 25 days
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// process our fake issue list
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('skips stale message on issues when skip-stale-issue-message is set', async () => {
|
|
||||||
let lastUpdate = new Date();
|
|
||||||
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should be marked stale but not closed',
|
|
||||||
lastUpdate.toString(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
|
||||||
opts.daysBeforeClose = 20; // closes after 25 days
|
|
||||||
opts.skipStaleIssueMessage = true;
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// for sake of testing, mocking private function
|
|
||||||
const markSpy = jest.spyOn(processor as any, 'markStale');
|
|
||||||
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
// issue should be staled
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
|
||||||
|
|
||||||
// comment should not be created
|
|
||||||
expect(markSpy).toHaveBeenCalledWith(
|
|
||||||
TestIssueList[0],
|
|
||||||
opts.staleIssueMessage,
|
|
||||||
opts.staleIssueLabel,
|
|
||||||
// this option is skipMessage
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('skips stale message on prs when skip-stale-pr-message is set', async () => {
|
|
||||||
let lastUpdate = new Date();
|
|
||||||
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should be marked stale but not closed',
|
|
||||||
lastUpdate.toString(),
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
|
||||||
opts.daysBeforeClose = 20; // closes after 25 days
|
|
||||||
opts.skipStalePrMessage = true;
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// for sake of testing, mocking private function
|
|
||||||
const markSpy = jest.spyOn(processor as any, 'markStale');
|
|
||||||
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
// issue should be staled
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
|
||||||
|
|
||||||
// comment should not be created
|
|
||||||
expect(markSpy).toHaveBeenCalledWith(
|
|
||||||
TestIssueList[0],
|
|
||||||
opts.stalePrMessage,
|
|
||||||
opts.stalePrLabel,
|
|
||||||
// this option is skipMessage
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('not providing state takes precedence over skipStaleIssueMessage', async () => {
|
|
||||||
let lastUpdate = new Date();
|
|
||||||
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should be marked stale but not closed',
|
|
||||||
lastUpdate.toString(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
|
||||||
opts.daysBeforeClose = 20; // closes after 25 days
|
|
||||||
opts.skipStalePrMessage = true;
|
|
||||||
opts.staleIssueMessage = '';
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
// issue should be staled
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('not providing stalePrMessage takes precedence over skipStalePrMessage', async () => {
|
|
||||||
let lastUpdate = new Date();
|
|
||||||
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
|
||||||
const TestIssueList: Issue[] = [
|
|
||||||
generateIssue(
|
|
||||||
1,
|
|
||||||
'An issue that should be marked stale but not closed',
|
|
||||||
lastUpdate.toString(),
|
|
||||||
true
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const opts = {...DefaultProcessorOptions};
|
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
|
||||||
opts.daysBeforeClose = 20; // closes after 25 days
|
|
||||||
opts.skipStalePrMessage = true;
|
|
||||||
opts.stalePrMessage = '';
|
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
|
||||||
opts,
|
|
||||||
async p => (p == 1 ? TestIssueList : []),
|
|
||||||
async (num, dt) => [],
|
|
||||||
async (issue, label) => new Date().toDateString()
|
|
||||||
);
|
|
||||||
|
|
||||||
await processor.processIssues(1);
|
|
||||||
|
|
||||||
// issue should be staled
|
|
||||||
expect(processor.closedIssues.length).toEqual(0);
|
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
|
||||||
});
|
|
||||||
3412
__tests__/milestones.spec.ts
Normal file
3412
__tests__/milestones.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
1152
__tests__/only-labels.spec.ts
Normal file
1152
__tests__/only-labels.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
140
action.yml
140
action.yml
@@ -4,58 +4,162 @@ author: 'GitHub'
|
|||||||
inputs:
|
inputs:
|
||||||
repo-token:
|
repo-token:
|
||||||
description: 'Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`.'
|
description: 'Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`.'
|
||||||
required: true
|
required: false
|
||||||
|
default: ${{ github.token }}
|
||||||
stale-issue-message:
|
stale-issue-message:
|
||||||
description: 'The message to post on the issue when tagging it. If none provided, will not mark issues stale.'
|
description: 'The message to post on the issue when tagging it. If none provided, will not mark issues stale.'
|
||||||
|
required: false
|
||||||
stale-pr-message:
|
stale-pr-message:
|
||||||
description: 'The message to post on the pr when tagging it. If none provided, will not mark pull requests stale.'
|
description: 'The message to post on the pull request when tagging it. If none provided, will not mark pull requests stale.'
|
||||||
|
required: false
|
||||||
close-issue-message:
|
close-issue-message:
|
||||||
description: 'The message to post on the issue when closing it. If none provided, will not comment when closing an issue.'
|
description: 'The message to post on the issue when closing it. If none provided, will not comment when closing an issue.'
|
||||||
|
required: false
|
||||||
close-pr-message:
|
close-pr-message:
|
||||||
description: 'The message to post on the pr when closing it. If none provided, will not comment when closing a pull requests.'
|
description: 'The message to post on the pull request when closing it. If none provided, will not comment when closing a pull requests.'
|
||||||
|
required: false
|
||||||
days-before-stale:
|
days-before-stale:
|
||||||
description: 'The number of days old an issue can be before marking it stale. Set to -1 to never mark issues or pull requests as stale automatically.'
|
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.'
|
||||||
default: 60
|
required: false
|
||||||
|
default: '60'
|
||||||
|
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-close:
|
days-before-close:
|
||||||
description: 'The number of days to wait to close an issue or pull request after it being marked stale. Set to -1 to never close stale issues.'
|
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.'
|
||||||
default: 7
|
required: false
|
||||||
|
default: '7'
|
||||||
|
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
|
||||||
|
days-before-pr-close:
|
||||||
|
description: 'The number of days to wait to close a pull request after it being marked stale. Set to -1 to never close stale pull requests. Override "days-before-close" option regarding only the pull requests.'
|
||||||
|
required: false
|
||||||
stale-issue-label:
|
stale-issue-label:
|
||||||
description: 'The label to apply when an issue is stale.'
|
description: 'The label to apply when an issue is stale.'
|
||||||
|
required: false
|
||||||
default: 'Stale'
|
default: 'Stale'
|
||||||
close-issue-label:
|
close-issue-label:
|
||||||
description: 'The label to apply when an issue is closed.'
|
description: 'The label to apply when an issue is closed.'
|
||||||
|
required: false
|
||||||
exempt-issue-labels:
|
exempt-issue-labels:
|
||||||
description: 'The labels to apply when an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
|
description: 'The labels that mean an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2").'
|
||||||
default: ''
|
default: ''
|
||||||
|
required: false
|
||||||
stale-pr-label:
|
stale-pr-label:
|
||||||
description: 'The label to apply when a pull request is stale.'
|
description: 'The label to apply when a pull request is stale.'
|
||||||
default: 'Stale'
|
default: 'Stale'
|
||||||
|
required: false
|
||||||
close-pr-label:
|
close-pr-label:
|
||||||
description: 'The label to apply when a pull request is closed.'
|
description: 'The label to apply when a pull request is closed.'
|
||||||
|
required: false
|
||||||
exempt-pr-labels:
|
exempt-pr-labels:
|
||||||
description: 'The labels to apply when a pull request is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
|
description: 'The labels that mean a pull request is exempt from being marked as stale. Separate multiple labels with commas (eg. "label1,label2").'
|
||||||
default: ''
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-milestones:
|
||||||
|
description: 'The milestones that mean an issue or a pull request is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2").'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-issue-milestones:
|
||||||
|
description: 'The milestones that mean an issue is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the issues.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-pr-milestones:
|
||||||
|
description: 'The milestones that mean a pull request is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the pull requests.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-all-milestones:
|
||||||
|
description: 'Exempt all issues and pull requests with milestones from being marked as stale. Default to false.'
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
|
exempt-all-issue-milestones:
|
||||||
|
description: 'Exempt all issues with milestones from being marked as stale. Override "exempt-all-milestones" option regarding only the issues.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-all-pr-milestones:
|
||||||
|
description: 'Exempt all pull requests with milestones from being marked as stale. Override "exempt-all-milestones" option regarding only the pull requests.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
only-labels:
|
only-labels:
|
||||||
description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels.'
|
description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels.'
|
||||||
default: ''
|
default: ''
|
||||||
|
required: false
|
||||||
|
any-of-labels:
|
||||||
|
description: 'Only issues or pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
only-issue-labels:
|
||||||
|
description: 'Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the issues.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
only-pr-labels:
|
||||||
|
description: 'Only pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the pull requests.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
operations-per-run:
|
operations-per-run:
|
||||||
description: 'The maximum number of operations per run, used to control rate limiting.'
|
description: 'The maximum number of operations per run, used to control rate limiting (GitHub API CRUD related).'
|
||||||
default: 30
|
default: '30'
|
||||||
|
required: false
|
||||||
remove-stale-when-updated:
|
remove-stale-when-updated:
|
||||||
description: 'Remove stale labels from issues when they are updated or commented on.'
|
description: 'Remove stale labels from issues when they are updated or commented on.'
|
||||||
default: true
|
default: 'true'
|
||||||
|
required: false
|
||||||
debug-only:
|
debug-only:
|
||||||
description: 'Run the processor in debug mode without actually performing any operations on live issues.'
|
description: 'Run the processor in debug mode without actually performing any operations on live issues.'
|
||||||
default: false
|
default: 'false'
|
||||||
|
required: false
|
||||||
ascending:
|
ascending:
|
||||||
description: 'The order to get issues or pull requests. Defaults to false, which is descending'
|
description: 'The order to get issues or pull requests. Defaults to false, which is descending.'
|
||||||
default: false
|
default: 'false'
|
||||||
|
required: false
|
||||||
skip-stale-pr-message:
|
skip-stale-pr-message:
|
||||||
description: 'Skip adding stale message when marking a pull request as stale.'
|
description: 'Skip adding stale message when marking a pull request as stale.'
|
||||||
default: false
|
default: 'false'
|
||||||
|
required: false
|
||||||
skip-stale-issue-message:
|
skip-stale-issue-message:
|
||||||
description: 'Skip adding stale message when marking an issue as stale.'
|
description: 'Skip adding stale message when marking an issue as stale.'
|
||||||
default: false
|
default: 'false'
|
||||||
|
required: false
|
||||||
|
delete-branch:
|
||||||
|
description: 'Delete the git branch after closing a stale pull request.'
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
|
start-date:
|
||||||
|
description: 'The date used to skip the stale action on issue/pull request created before it (ISO 8601 or RFC 2822).'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-assignees:
|
||||||
|
description: 'The assignees which exempt an issue or a pull request from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2").'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-issue-assignees:
|
||||||
|
description: 'The assignees which exempt an issue from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). Override "exempt-assignees" option regarding only the issues.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-pr-assignees:
|
||||||
|
description: 'The assignees which exempt a pull request from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). Override "exempt-assignees" option regarding only the pull requests.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-all-assignees:
|
||||||
|
description: 'Exempt all issues and pull requests with assignees from being marked as stale. Default to false.'
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
|
exempt-all-issue-assignees:
|
||||||
|
description: 'Exempt all issues with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the issues.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-all-pr-assignees:
|
||||||
|
description: 'Exempt all pull requests with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the pull requests.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
enable-statistics:
|
||||||
|
description: 'Display some statistics at the end regarding the stale workflow (only when the logs are enabled).'
|
||||||
|
default: 'true'
|
||||||
|
required: false
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|||||||
1997
dist/index.js
vendored
1997
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,10 @@ module.exports = {
|
|||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
moduleFileExtensions: ['js', 'ts'],
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
testMatch: ['**/*.test.ts'],
|
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
|
||||||
testRunner: 'jest-circus/runner',
|
testRunner: 'jest-circus/runner',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.ts$': 'ts-jest'
|
'^.+\\.ts$': 'ts-jest'
|
||||||
},
|
},
|
||||||
verbose: true
|
verbose: true
|
||||||
}
|
};
|
||||||
|
|||||||
6412
package-lock.json
generated
6412
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@@ -5,12 +5,17 @@
|
|||||||
"description": "Marks old issues and PRs as stale",
|
"description": "Marks old issues and PRs as stale",
|
||||||
"main": "lib/main.js",
|
"main": "lib/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc --project tsconfig.app.json",
|
||||||
"format": "prettier --write **/*.ts",
|
"format": "prettier --write --ignore-unknown **/*.{md,json,yml,ts}",
|
||||||
"format-check": "prettier --check **/*.ts",
|
"format-check": "prettier --check --ignore-unknown **/*.{md,json,yml,ts}",
|
||||||
"lint": "eslint src/**/*.ts",
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"lint:fix": "eslint src/**/*.ts --fix",
|
||||||
|
"lint:all": "npm run format-check && npm run lint",
|
||||||
|
"lint:all:fix": "npm run format && npm run lint:fix",
|
||||||
"pack": "ncc build",
|
"pack": "ncc build",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
"test:only-errors": "jest --reporters jest-silent-reporter --silent",
|
||||||
|
"test:watch": "jest --watch --notify --expandf",
|
||||||
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
|
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -27,23 +32,27 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/github": "^4.0.0",
|
"@actions/github": "^4.0.0",
|
||||||
"@octokit/rest": "^18.0.4",
|
"@octokit/rest": "^18.3.3",
|
||||||
"semver": "^7.3.2"
|
"lodash.deburr": "^4.1.0",
|
||||||
|
"semver": "^7.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/semver": "^7.3.1",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/jest": "^26.0.10",
|
"@types/lodash.deburr": "^4.1.6",
|
||||||
"@types/node": "^14.10.0",
|
"@types/node": "^14.14.31",
|
||||||
"@typescript-eslint/parser": "^3.10.1",
|
"@types/semver": "^7.3.4",
|
||||||
"@vercel/ncc": "^0.24.0",
|
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||||
"eslint": "^7.7.0",
|
"@typescript-eslint/parser": "^4.16.1",
|
||||||
"eslint-plugin-github": "^4.0.1",
|
"@vercel/ncc": "^0.27.0",
|
||||||
"eslint-plugin-jest": "^23.20.0",
|
"eslint": "^7.21.0",
|
||||||
"jest": "^24.9.0",
|
"eslint-plugin-github": "^4.1.2",
|
||||||
"jest-circus": "^26.4.2",
|
"eslint-plugin-jest": "^24.1.5",
|
||||||
"js-yaml": "^3.14.0",
|
"jest": "^26.6.3",
|
||||||
"prettier": "^2.1.1",
|
"jest-circus": "^26.6.3",
|
||||||
"ts-jest": "^24.2.0",
|
"jest-silent-reporter": "^0.4.0",
|
||||||
"typescript": "^4.0.2"
|
"js-yaml": "^4.0.0",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"ts-jest": "^26.5.3",
|
||||||
|
"typescript": "^4.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,513 +0,0 @@
|
|||||||
import * as core from '@actions/core';
|
|
||||||
import {context, getOctokit} from '@actions/github';
|
|
||||||
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
|
||||||
|
|
||||||
export interface Issue {
|
|
||||||
title: string;
|
|
||||||
number: number;
|
|
||||||
updated_at: string;
|
|
||||||
labels: Label[];
|
|
||||||
pull_request: any;
|
|
||||||
state: string;
|
|
||||||
locked: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface User {
|
|
||||||
type: string;
|
|
||||||
login: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Comment {
|
|
||||||
user: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IssueEvent {
|
|
||||||
created_at: string;
|
|
||||||
event: string;
|
|
||||||
label: Label;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Label {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IssueProcessorOptions {
|
|
||||||
repoToken: string;
|
|
||||||
staleIssueMessage: string;
|
|
||||||
stalePrMessage: string;
|
|
||||||
closeIssueMessage: string;
|
|
||||||
closePrMessage: string;
|
|
||||||
daysBeforeStale: number;
|
|
||||||
daysBeforeClose: number;
|
|
||||||
staleIssueLabel: string;
|
|
||||||
closeIssueLabel: string;
|
|
||||||
exemptIssueLabels: string;
|
|
||||||
stalePrLabel: string;
|
|
||||||
closePrLabel: string;
|
|
||||||
exemptPrLabels: string;
|
|
||||||
onlyLabels: string;
|
|
||||||
operationsPerRun: number;
|
|
||||||
removeStaleWhenUpdated: boolean;
|
|
||||||
debugOnly: boolean;
|
|
||||||
ascending: boolean;
|
|
||||||
skipStaleIssueMessage: boolean;
|
|
||||||
skipStalePrMessage: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Handle processing of issues for staleness/closure.
|
|
||||||
*/
|
|
||||||
export class IssueProcessor {
|
|
||||||
readonly client: any; // need to make this the correct type
|
|
||||||
readonly options: IssueProcessorOptions;
|
|
||||||
private operationsLeft = 0;
|
|
||||||
|
|
||||||
readonly staleIssues: Issue[] = [];
|
|
||||||
readonly closedIssues: Issue[] = [];
|
|
||||||
readonly removedLabelIssues: Issue[] = [];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
options: IssueProcessorOptions,
|
|
||||||
getIssues?: (page: number) => Promise<Issue[]>,
|
|
||||||
listIssueComments?: (
|
|
||||||
issueNumber: number,
|
|
||||||
sinceDate: string
|
|
||||||
) => Promise<Comment[]>,
|
|
||||||
getLabelCreationDate?: (
|
|
||||||
issue: Issue,
|
|
||||||
label: string
|
|
||||||
) => Promise<string | undefined>
|
|
||||||
) {
|
|
||||||
this.options = options;
|
|
||||||
this.operationsLeft = options.operationsPerRun;
|
|
||||||
this.client = getOctokit(options.repoToken);
|
|
||||||
|
|
||||||
if (getIssues) {
|
|
||||||
this.getIssues = getIssues;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listIssueComments) {
|
|
||||||
this.listIssueComments = listIssueComments;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getLabelCreationDate) {
|
|
||||||
this.getLabelCreationDate = getLabelCreationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.debugOnly) {
|
|
||||||
core.warning(
|
|
||||||
'Executing in debug mode. Debug output will be written but no issues will be processed.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async processIssues(page = 1): Promise<number> {
|
|
||||||
// get the next batch of issues
|
|
||||||
const issues: Issue[] = await this.getIssues(page);
|
|
||||||
this.operationsLeft -= 1;
|
|
||||||
|
|
||||||
if (issues.length <= 0) {
|
|
||||||
core.info('No more issues found to process. Exiting.');
|
|
||||||
return this.operationsLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const issue of issues.values()) {
|
|
||||||
const isPr = !!issue.pull_request;
|
|
||||||
|
|
||||||
core.info(
|
|
||||||
`Found issue: issue #${issue.number} - ${issue.title} last updated ${issue.updated_at} (is pr? ${isPr})`
|
|
||||||
);
|
|
||||||
|
|
||||||
// calculate string based messages for this issue
|
|
||||||
const staleMessage: string = isPr
|
|
||||||
? this.options.stalePrMessage
|
|
||||||
: this.options.staleIssueMessage;
|
|
||||||
const closeMessage: string = isPr
|
|
||||||
? this.options.closePrMessage
|
|
||||||
: this.options.closeIssueMessage;
|
|
||||||
const staleLabel: string = isPr
|
|
||||||
? this.options.stalePrLabel
|
|
||||||
: this.options.staleIssueLabel;
|
|
||||||
const closeLabel: string = isPr
|
|
||||||
? this.options.closePrLabel
|
|
||||||
: this.options.closeIssueLabel;
|
|
||||||
const exemptLabels = IssueProcessor.parseCommaSeparatedString(
|
|
||||||
isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels
|
|
||||||
);
|
|
||||||
const skipMessage = isPr
|
|
||||||
? this.options.skipStalePrMessage
|
|
||||||
: this.options.skipStaleIssueMessage;
|
|
||||||
const issueType: string = isPr ? 'pr' : 'issue';
|
|
||||||
const shouldMarkWhenStale = this.options.daysBeforeStale > -1;
|
|
||||||
|
|
||||||
if (!staleMessage && shouldMarkWhenStale) {
|
|
||||||
core.info(`Skipping ${issueType} due to empty stale message`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (issue.state === 'closed') {
|
|
||||||
core.info(`Skipping ${issueType} because it is closed`);
|
|
||||||
continue; // don't process closed issues
|
|
||||||
}
|
|
||||||
|
|
||||||
if (issue.locked) {
|
|
||||||
core.info(`Skipping ${issueType} because it is locked`);
|
|
||||||
continue; // don't process locked issues
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
exemptLabels.some((exemptLabel: string) =>
|
|
||||||
IssueProcessor.isLabeled(issue, exemptLabel)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
core.info(`Skipping ${issueType} because it has an exempt label`);
|
|
||||||
continue; // don't process exempt issues
|
|
||||||
}
|
|
||||||
|
|
||||||
// does this issue have a stale label?
|
|
||||||
let isStale = IssueProcessor.isLabeled(issue, staleLabel);
|
|
||||||
|
|
||||||
// should this issue be marked stale?
|
|
||||||
const shouldBeStale = !IssueProcessor.updatedSince(
|
|
||||||
issue.updated_at,
|
|
||||||
this.options.daysBeforeStale
|
|
||||||
);
|
|
||||||
|
|
||||||
// determine if this issue needs to be marked stale first
|
|
||||||
if (!isStale && shouldBeStale && shouldMarkWhenStale) {
|
|
||||||
core.info(
|
|
||||||
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
|
|
||||||
);
|
|
||||||
await this.markStale(issue, staleMessage, staleLabel, skipMessage);
|
|
||||||
isStale = true; // this issue is now considered stale
|
|
||||||
}
|
|
||||||
|
|
||||||
// process the issue if it was marked stale
|
|
||||||
if (isStale) {
|
|
||||||
core.info(`Found a stale ${issueType}`);
|
|
||||||
await this.processStaleIssue(
|
|
||||||
issue,
|
|
||||||
issueType,
|
|
||||||
staleLabel,
|
|
||||||
closeMessage,
|
|
||||||
closeLabel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.operationsLeft <= 0) {
|
|
||||||
core.warning('Reached max number of operations to process. Exiting.');
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the next batch
|
|
||||||
return this.processIssues(page + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle all of the stale issue logic when we find a stale issue
|
|
||||||
private async processStaleIssue(
|
|
||||||
issue: Issue,
|
|
||||||
issueType: string,
|
|
||||||
staleLabel: string,
|
|
||||||
closeMessage?: string,
|
|
||||||
closeLabel?: string
|
|
||||||
) {
|
|
||||||
const markedStaleOn: string =
|
|
||||||
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
|
|
||||||
core.info(`Issue #${issue.number} marked stale on: ${markedStaleOn}`);
|
|
||||||
|
|
||||||
const issueHasComments: boolean = await this.hasCommentsSince(
|
|
||||||
issue,
|
|
||||||
markedStaleOn
|
|
||||||
);
|
|
||||||
core.info(
|
|
||||||
`Issue #${issue.number} has been commented on: ${issueHasComments}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const issueHasUpdate: boolean = IssueProcessor.updatedSince(
|
|
||||||
issue.updated_at,
|
|
||||||
this.options.daysBeforeClose
|
|
||||||
);
|
|
||||||
core.info(`Issue #${issue.number} has been updated: ${issueHasUpdate}`);
|
|
||||||
|
|
||||||
// should we un-stale this issue?
|
|
||||||
if (this.options.removeStaleWhenUpdated && issueHasComments) {
|
|
||||||
core.info(
|
|
||||||
`Issue #${issue.number} is no longer stale. Removing stale label.`
|
|
||||||
);
|
|
||||||
await this.removeLabel(issue, staleLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// now start closing logic
|
|
||||||
if (this.options.daysBeforeClose < 0) {
|
|
||||||
return; // nothing to do because we aren't closing stale issues
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!issueHasComments && !issueHasUpdate) {
|
|
||||||
core.info(
|
|
||||||
`Closing ${issueType} because it was last updated on ${issue.updated_at}`
|
|
||||||
);
|
|
||||||
await this.closeIssue(issue, closeMessage, closeLabel);
|
|
||||||
} else {
|
|
||||||
core.info(
|
|
||||||
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checks to see if a given issue is still stale (has had activity on it)
|
|
||||||
private async hasCommentsSince(
|
|
||||||
issue: Issue,
|
|
||||||
sinceDate: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
core.info(
|
|
||||||
`Checking for comments on issue #${issue.number} since ${sinceDate}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!sinceDate) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find any comments since the date
|
|
||||||
const comments = await this.listIssueComments(issue.number, sinceDate);
|
|
||||||
|
|
||||||
const filteredComments = comments.filter(
|
|
||||||
comment =>
|
|
||||||
comment.user.type === 'User' && comment.user.login !== context.actor
|
|
||||||
);
|
|
||||||
|
|
||||||
core.info(
|
|
||||||
`Comments not made by ${context.actor} or another bot: ${filteredComments.length}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// if there are any user comments returned
|
|
||||||
return filteredComments.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// grab comments for an issue since a given date
|
|
||||||
private async listIssueComments(
|
|
||||||
issueNumber: number,
|
|
||||||
sinceDate: string
|
|
||||||
): Promise<Comment[]> {
|
|
||||||
// find any comments since date on the given issue
|
|
||||||
try {
|
|
||||||
const comments = await this.client.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
since: sinceDate
|
|
||||||
});
|
|
||||||
return comments.data;
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`List issue comments error: ${error.message}`);
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// grab issues from github in baches of 100
|
|
||||||
private async getIssues(page: number): Promise<Issue[]> {
|
|
||||||
// generate type for response
|
|
||||||
const endpoint = this.client.issues.listForRepo;
|
|
||||||
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
|
|
||||||
{
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'open',
|
|
||||||
labels: this.options.onlyLabels,
|
|
||||||
per_page: 100,
|
|
||||||
direction: this.options.ascending ? 'asc' : 'desc',
|
|
||||||
page
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return issueResult.data;
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`Get issues for repo error: ${error.message}`);
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark an issue as stale with a comment and a label
|
|
||||||
private async markStale(
|
|
||||||
issue: Issue,
|
|
||||||
staleMessage: string,
|
|
||||||
staleLabel: string,
|
|
||||||
skipMessage: boolean
|
|
||||||
): Promise<void> {
|
|
||||||
core.info(`Marking issue #${issue.number} - ${issue.title} as stale`);
|
|
||||||
|
|
||||||
this.staleIssues.push(issue);
|
|
||||||
|
|
||||||
this.operationsLeft -= 2;
|
|
||||||
|
|
||||||
// if the issue is being marked stale, 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 (this.options.debugOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!skipMessage) {
|
|
||||||
try {
|
|
||||||
await this.client.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
body: staleMessage
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`Error creating a comment: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.client.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
labels: [staleLabel]
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`Error adding a label: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close an issue based on staleness
|
|
||||||
private async closeIssue(
|
|
||||||
issue: Issue,
|
|
||||||
closeMessage?: string,
|
|
||||||
closeLabel?: string
|
|
||||||
): Promise<void> {
|
|
||||||
core.info(
|
|
||||||
`Closing issue #${issue.number} - ${issue.title} for being stale`
|
|
||||||
);
|
|
||||||
|
|
||||||
this.closedIssues.push(issue);
|
|
||||||
|
|
||||||
this.operationsLeft -= 1;
|
|
||||||
|
|
||||||
if (this.options.debugOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closeMessage) {
|
|
||||||
try {
|
|
||||||
await this.client.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
body: closeMessage
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`Error creating a comment: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closeLabel) {
|
|
||||||
try {
|
|
||||||
await this.client.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
labels: [closeLabel]
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`Error adding a label: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.client.issues.update({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
state: 'closed'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`Error updating an issue: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a label from an issue
|
|
||||||
private async removeLabel(issue: Issue, label: string): Promise<void> {
|
|
||||||
core.info(
|
|
||||||
`Removing label ${label} from issue #${issue.number} - ${issue.title}`
|
|
||||||
);
|
|
||||||
|
|
||||||
this.removedLabelIssues.push(issue);
|
|
||||||
|
|
||||||
this.operationsLeft -= 1;
|
|
||||||
|
|
||||||
if (this.options.debugOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.client.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
name: encodeURIComponent(label) // A label can have a "?" in the name
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
core.error(`Error removing a label: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the creation date of a given label on an issue (or nothing if no label existed)
|
|
||||||
///see https://developer.github.com/v3/activity/events/
|
|
||||||
private async getLabelCreationDate(
|
|
||||||
issue: Issue,
|
|
||||||
label: string
|
|
||||||
): Promise<string | undefined> {
|
|
||||||
core.info(`Checking for label ${label} on issue #${issue.number}`);
|
|
||||||
|
|
||||||
this.operationsLeft -= 1;
|
|
||||||
|
|
||||||
const options = this.client.issues.listEvents.endpoint.merge({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
per_page: 100,
|
|
||||||
issue_number: issue.number
|
|
||||||
});
|
|
||||||
|
|
||||||
const events: IssueEvent[] = await this.client.paginate(options);
|
|
||||||
const reversedEvents = events.reverse();
|
|
||||||
|
|
||||||
const staleLabeledEvent = reversedEvents.find(
|
|
||||||
event => event.event === 'labeled' && event.label.name === label
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!staleLabeledEvent) {
|
|
||||||
// Must be old rather than labeled
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return staleLabeledEvent.created_at;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static isLabeled(issue: Issue, label: string): boolean {
|
|
||||||
const labelComparer: (l: Label) => boolean = l =>
|
|
||||||
label.localeCompare(l.name, undefined, {sensitivity: 'accent'}) === 0;
|
|
||||||
return issue.labels.filter(labelComparer).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static updatedSince(timestamp: string, num_days: number): boolean {
|
|
||||||
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
|
|
||||||
const millisSinceLastUpdated =
|
|
||||||
new Date().getTime() - new Date(timestamp).getTime();
|
|
||||||
|
|
||||||
return millisSinceLastUpdated <= daysInMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static parseCommaSeparatedString(s: string): string[] {
|
|
||||||
// String.prototype.split defaults to [''] when called on an empty string
|
|
||||||
// In this case, we'd prefer to just return an empty array indicating no labels
|
|
||||||
if (!s.length) return [];
|
|
||||||
return s.split(',').map(l => l.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
594
src/classes/assignees.spec.ts
Normal file
594
src/classes/assignees.spec.ts
Normal file
@@ -0,0 +1,594 @@
|
|||||||
|
import {DefaultProcessorOptions} from '../../__tests__/constants/default-processor-options';
|
||||||
|
import {generateIIssue} from '../../__tests__/functions/generate-iissue';
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
import {Assignees} from './assignees';
|
||||||
|
|
||||||
|
describe('Assignees', (): void => {
|
||||||
|
let assignees: Assignees;
|
||||||
|
let optionsInterface: IIssuesProcessorOptions;
|
||||||
|
let issue: Issue;
|
||||||
|
let issueInterface: IIssue;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface = {...DefaultProcessorOptions};
|
||||||
|
issueInterface = generateIIssue();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('shouldExemptAssignees()', (): void => {
|
||||||
|
describe('when the given issue is not a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptAssignees = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an issue with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueAssignees = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an issue with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueAssignees =
|
||||||
|
'dummy-exempt-issue-assignee';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt issue assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt issue assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-issue-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptAssignees = 'dummy-exempt-assignee';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an issue with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueAssignees = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an issue with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueAssignees =
|
||||||
|
'dummy-exempt-issue-assignee';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt issue assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt issue assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-issue-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue is a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptAssignees = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a pull request with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrAssignees = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a pull request with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrAssignees = 'dummy-exempt-pr-assignee';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt pull request assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt pull request assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-pr-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptAssignees = 'dummy-exempt-assignee';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a pull request with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrAssignees = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a pull request with an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrAssignees = 'dummy-exempt-pr-assignee';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have an assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt pull request assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt pull request assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-pr-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-exempt-assignee'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
assignees = new Assignees(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = assignees.shouldExemptAssignees();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
251
src/classes/assignees.ts
Normal file
251
src/classes/assignees.ts
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import deburr from 'lodash.deburr';
|
||||||
|
import {wordsToList} from '../functions/words-to-list';
|
||||||
|
import {IAssignee} from '../interfaces/assignee';
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
import {IssueLogger} from './loggers/issue-logger';
|
||||||
|
|
||||||
|
type CleanAssignee = string;
|
||||||
|
|
||||||
|
export class Assignees {
|
||||||
|
private static _cleanAssignee(assignee: Readonly<string>): CleanAssignee {
|
||||||
|
return deburr(assignee.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _options: IIssuesProcessorOptions;
|
||||||
|
private readonly _issue: Issue;
|
||||||
|
private readonly _issueLogger: IssueLogger;
|
||||||
|
|
||||||
|
constructor(options: Readonly<IIssuesProcessorOptions>, issue: Issue) {
|
||||||
|
this._options = options;
|
||||||
|
this._issue = issue;
|
||||||
|
this._issueLogger = new IssueLogger(issue);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldExemptAssignees(): boolean {
|
||||||
|
if (!this._issue.hasAssignees) {
|
||||||
|
this._issueLogger.info('This $$type has no assignee');
|
||||||
|
this._logSkip();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._shouldExemptAllAssignees()) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'Skipping $$type because it has an exempt assignee'
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptAssignees: string[] = this._getExemptAssignees();
|
||||||
|
|
||||||
|
if (exemptAssignees.length === 0) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
`No option was specified to skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
this._logSkip();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._issueLogger.info(
|
||||||
|
`Found ${exemptAssignees.length} assignee${
|
||||||
|
exemptAssignees.length > 1 ? 's' : ''
|
||||||
|
} on this $$type`
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasExemptAssignee: boolean = exemptAssignees.some(
|
||||||
|
(exemptAssignee: Readonly<string>): boolean =>
|
||||||
|
this._hasAssignee(exemptAssignee)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasExemptAssignee) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'No assignee on this $$type can exempt the stale process'
|
||||||
|
);
|
||||||
|
this._logSkip();
|
||||||
|
} else {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'Skipping this $$type because it has an exempt assignee'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasExemptAssignee;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptAssignees(): string[] {
|
||||||
|
return this._issue.isPullRequest
|
||||||
|
? this._getExemptPullRequestAssignees()
|
||||||
|
: this._getExemptIssueAssignees();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptIssueAssignees(): string[] {
|
||||||
|
if (this._options.exemptIssueAssignees === '') {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptIssueAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._options.exemptAssignees === '') {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||||
|
);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptAssignees: string[] = wordsToList(
|
||||||
|
this._options.exemptAssignees
|
||||||
|
);
|
||||||
|
|
||||||
|
this._issueLogger.info(
|
||||||
|
`The option "exemptAssignees" is set. ${
|
||||||
|
exemptAssignees.length
|
||||||
|
} assignee${
|
||||||
|
exemptAssignees.length === 1 ? '' : 's'
|
||||||
|
} can skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
|
||||||
|
return exemptAssignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptAssignees: string[] = wordsToList(
|
||||||
|
this._options.exemptIssueAssignees
|
||||||
|
);
|
||||||
|
|
||||||
|
this._issueLogger.info(
|
||||||
|
`The option "exemptIssueAssignees" is set. ${
|
||||||
|
exemptAssignees.length
|
||||||
|
} assignee${
|
||||||
|
exemptAssignees.length === 1 ? '' : 's'
|
||||||
|
} can skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
|
||||||
|
return exemptAssignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptPullRequestAssignees(): string[] {
|
||||||
|
if (this._options.exemptPrAssignees === '') {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptPrAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._options.exemptAssignees === '') {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||||
|
);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptAssignees: string[] = wordsToList(
|
||||||
|
this._options.exemptAssignees
|
||||||
|
);
|
||||||
|
|
||||||
|
this._issueLogger.info(
|
||||||
|
`The option "exemptAssignees" is set. ${
|
||||||
|
exemptAssignees.length
|
||||||
|
} assignee${
|
||||||
|
exemptAssignees.length === 1 ? '' : 's'
|
||||||
|
} can skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
|
||||||
|
return exemptAssignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptAssignees: string[] = wordsToList(
|
||||||
|
this._options.exemptPrAssignees
|
||||||
|
);
|
||||||
|
|
||||||
|
this._issueLogger.info(
|
||||||
|
`The option "exemptPrAssignees" is set. ${
|
||||||
|
exemptAssignees.length
|
||||||
|
} assignee${
|
||||||
|
exemptAssignees.length === 1 ? '' : 's'
|
||||||
|
} can skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
|
||||||
|
return exemptAssignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hasAssignee(assignee: Readonly<string>): boolean {
|
||||||
|
const cleanAssignee: CleanAssignee = Assignees._cleanAssignee(assignee);
|
||||||
|
|
||||||
|
return this._issue.assignees.some(
|
||||||
|
(issueAssignee: Readonly<IAssignee>): boolean => {
|
||||||
|
const isSameAssignee: boolean =
|
||||||
|
cleanAssignee === Assignees._cleanAssignee(issueAssignee.login);
|
||||||
|
|
||||||
|
if (isSameAssignee) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
`@${issueAssignee.login} is assigned on this $$type and is an exempt assignee`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSameAssignee;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldExemptAllAssignees(): boolean {
|
||||||
|
return this._issue.isPullRequest
|
||||||
|
? this._shouldExemptAllPullRequestAssignees()
|
||||||
|
: this._shouldExemptAllIssueAssignees();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldExemptAllIssueAssignees(): boolean {
|
||||||
|
if (this._options.exemptAllIssueAssignees === true) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllIssueAssignees" is enabled. Any assignee on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (this._options.exemptAllIssueAssignees === false) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllIssueAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._logExemptAllAssigneesOption();
|
||||||
|
|
||||||
|
return this._options.exemptAllAssignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldExemptAllPullRequestAssignees(): boolean {
|
||||||
|
if (this._options.exemptAllPrAssignees === true) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllPrAssignees" is enabled. Any assignee on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (this._options.exemptAllPrAssignees === false) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllPrAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._logExemptAllAssigneesOption();
|
||||||
|
|
||||||
|
return this._options.exemptAllAssignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logExemptAllAssigneesOption(): void {
|
||||||
|
if (this._options.exemptAllAssignees) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllAssignees" is enabled. Any assignee on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logSkip(): void {
|
||||||
|
this._issueLogger.info('Skip the assignees checks');
|
||||||
|
}
|
||||||
|
}
|
||||||
286
src/classes/issue.spec.ts
Normal file
286
src/classes/issue.spec.ts
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
import {IAssignee} from '../interfaces/assignee';
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {ILabel} from '../interfaces/label';
|
||||||
|
import {IMilestone} from '../interfaces/milestone';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
|
||||||
|
describe('Issue', (): void => {
|
||||||
|
let issue: Issue;
|
||||||
|
let optionsInterface: IIssuesProcessorOptions;
|
||||||
|
let issueInterface: IIssue;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface = {
|
||||||
|
ascending: false,
|
||||||
|
closeIssueLabel: '',
|
||||||
|
closeIssueMessage: '',
|
||||||
|
closePrLabel: '',
|
||||||
|
closePrMessage: '',
|
||||||
|
daysBeforeClose: 0,
|
||||||
|
daysBeforeIssueClose: 0,
|
||||||
|
daysBeforeIssueStale: 0,
|
||||||
|
daysBeforePrClose: 0,
|
||||||
|
daysBeforePrStale: 0,
|
||||||
|
daysBeforeStale: 0,
|
||||||
|
debugOnly: false,
|
||||||
|
deleteBranch: false,
|
||||||
|
exemptIssueLabels: '',
|
||||||
|
exemptPrLabels: '',
|
||||||
|
onlyLabels: '',
|
||||||
|
onlyIssueLabels: '',
|
||||||
|
onlyPrLabels: '',
|
||||||
|
anyOfLabels: '',
|
||||||
|
operationsPerRun: 0,
|
||||||
|
removeStaleWhenUpdated: false,
|
||||||
|
repoToken: '',
|
||||||
|
skipStaleIssueMessage: false,
|
||||||
|
skipStalePrMessage: false,
|
||||||
|
staleIssueMessage: '',
|
||||||
|
stalePrMessage: '',
|
||||||
|
startDate: undefined,
|
||||||
|
stalePrLabel: 'dummy-stale-pr-label',
|
||||||
|
staleIssueLabel: 'dummy-stale-issue-label',
|
||||||
|
exemptMilestones: '',
|
||||||
|
exemptIssueMilestones: '',
|
||||||
|
exemptPrMilestones: '',
|
||||||
|
exemptAllMilestones: false,
|
||||||
|
exemptAllIssueMilestones: undefined,
|
||||||
|
exemptAllPrMilestones: undefined,
|
||||||
|
exemptAssignees: '',
|
||||||
|
exemptIssueAssignees: '',
|
||||||
|
exemptPrAssignees: '',
|
||||||
|
exemptAllAssignees: false,
|
||||||
|
exemptAllIssueAssignees: undefined,
|
||||||
|
exemptAllPrAssignees: undefined,
|
||||||
|
enableStatistics: false
|
||||||
|
};
|
||||||
|
issueInterface = {
|
||||||
|
title: 'dummy-title',
|
||||||
|
number: 8,
|
||||||
|
created_at: 'dummy-created-at',
|
||||||
|
updated_at: 'dummy-updated-at',
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
name: 'dummy-name'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pull_request: {},
|
||||||
|
state: 'dummy-state',
|
||||||
|
locked: false,
|
||||||
|
milestone: {
|
||||||
|
title: 'dummy-milestone'
|
||||||
|
},
|
||||||
|
assignees: [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor()', (): void => {
|
||||||
|
it('should set the title with the given issue title', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.title).toStrictEqual('dummy-title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the number with the given issue number', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.number).toStrictEqual(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the created_at with the given issue created_at', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.created_at).toStrictEqual('dummy-created-at');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the updated_at with the given issue updated_at', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.updated_at).toStrictEqual('dummy-updated-at');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the labels with the given issue labels', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.labels).toStrictEqual([
|
||||||
|
{
|
||||||
|
name: 'dummy-name'
|
||||||
|
} as ILabel
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the pull_request with the given issue pull_request', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.pull_request).toStrictEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the state with the given issue state', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.state).toStrictEqual('dummy-state');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the locked with the given issue locked', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.locked).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the milestone with the given issue milestone', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.milestone).toStrictEqual({
|
||||||
|
title: 'dummy-milestone'
|
||||||
|
} as IMilestone);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the assignees with the given issue assignees', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.assignees).toStrictEqual([
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
} as IAssignee
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not contains the stale label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issueInterface.labels = [];
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the isStale to false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.isStale).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains the stale label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issueInterface.labels = [
|
||||||
|
{
|
||||||
|
name: 'dummy-stale-issue-label'
|
||||||
|
} as ILabel
|
||||||
|
];
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the isStale to true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.isStale).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get isPullRequest', (): void => {
|
||||||
|
describe('when the issue pull_request is not set', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = issue.isPullRequest;
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the issue pull_request is set', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = {};
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = issue.isPullRequest;
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get staleLabel', (): void => {
|
||||||
|
describe('when the issue is not a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the issue stale label', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = issue.staleLabel;
|
||||||
|
|
||||||
|
expect(result).toStrictEqual('dummy-stale-issue-label');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue is a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = {};
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the pull request stale label', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = issue.staleLabel;
|
||||||
|
|
||||||
|
expect(result).toStrictEqual('dummy-stale-pr-label');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get hasAssignees', (): void => {
|
||||||
|
describe('when the issue has no assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [];
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = issue.hasAssignees;
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the issue has at least one assignee', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.assignees = [
|
||||||
|
{
|
||||||
|
login: 'dummy-login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = issue.hasAssignees;
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
60
src/classes/issue.ts
Normal file
60
src/classes/issue.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import {isLabeled} from '../functions/is-labeled';
|
||||||
|
import {isPullRequest} from '../functions/is-pull-request';
|
||||||
|
import {IAssignee} from '../interfaces/assignee';
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {ILabel} from '../interfaces/label';
|
||||||
|
import {IMilestone} from '../interfaces/milestone';
|
||||||
|
import {IsoDateString} from '../types/iso-date-string';
|
||||||
|
|
||||||
|
export class Issue implements IIssue {
|
||||||
|
private readonly _options: IIssuesProcessorOptions;
|
||||||
|
readonly title: string;
|
||||||
|
readonly number: number;
|
||||||
|
created_at: IsoDateString;
|
||||||
|
updated_at: IsoDateString;
|
||||||
|
readonly labels: ILabel[];
|
||||||
|
readonly pull_request: Object | null | undefined;
|
||||||
|
readonly state: string | 'closed' | 'open';
|
||||||
|
readonly locked: boolean;
|
||||||
|
readonly milestone: IMilestone | undefined;
|
||||||
|
readonly assignees: IAssignee[];
|
||||||
|
isStale: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: Readonly<IIssuesProcessorOptions>,
|
||||||
|
issue: Readonly<IIssue>
|
||||||
|
) {
|
||||||
|
this._options = options;
|
||||||
|
this.title = issue.title;
|
||||||
|
this.number = issue.number;
|
||||||
|
this.created_at = issue.created_at;
|
||||||
|
this.updated_at = issue.updated_at;
|
||||||
|
this.labels = issue.labels;
|
||||||
|
this.pull_request = issue.pull_request;
|
||||||
|
this.state = issue.state;
|
||||||
|
this.locked = issue.locked;
|
||||||
|
this.milestone = issue.milestone;
|
||||||
|
this.assignees = issue.assignees;
|
||||||
|
|
||||||
|
this.isStale = isLabeled(this, this.staleLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isPullRequest(): boolean {
|
||||||
|
return isPullRequest(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get staleLabel(): string {
|
||||||
|
return this._getStaleLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAssignees(): boolean {
|
||||||
|
return this.assignees.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getStaleLabel(): string {
|
||||||
|
return this.isPullRequest
|
||||||
|
? this._options.stalePrLabel
|
||||||
|
: this._options.staleIssueLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
749
src/classes/issues-processor.ts
Normal file
749
src/classes/issues-processor.ts
Normal file
@@ -0,0 +1,749 @@
|
|||||||
|
import {context, getOctokit} from '@actions/github';
|
||||||
|
import {GitHub} from '@actions/github/lib/utils';
|
||||||
|
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
||||||
|
import { coerce } from 'semver';
|
||||||
|
import {getHumanizedDate} from '../functions/dates/get-humanized-date';
|
||||||
|
import {isDateMoreRecentThan} from '../functions/dates/is-date-more-recent-than';
|
||||||
|
import {isValidDate} from '../functions/dates/is-valid-date';
|
||||||
|
import {isLabeled} from '../functions/is-labeled';
|
||||||
|
import {isPullRequest} from '../functions/is-pull-request';
|
||||||
|
import {shouldMarkWhenStale} from '../functions/should-mark-when-stale';
|
||||||
|
import {wordsToList} from '../functions/words-to-list';
|
||||||
|
import {IComment} from '../interfaces/comment';
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IIssueEvent} from '../interfaces/issue-event';
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {IPullRequest} from '../interfaces/pull-request';
|
||||||
|
import {Assignees} from './assignees';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
import {IssueLogger} from './loggers/issue-logger';
|
||||||
|
import {Logger} from './loggers/logger';
|
||||||
|
import {Milestones} from './milestones';
|
||||||
|
import {Statistics} from './statistics';
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Handle processing of issues for staleness/closure.
|
||||||
|
*/
|
||||||
|
export class IssuesProcessor {
|
||||||
|
private static _updatedSince(timestamp: string, num_days: number): boolean {
|
||||||
|
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
|
||||||
|
const millisSinceLastUpdated =
|
||||||
|
new Date().getTime() - new Date(timestamp).getTime();
|
||||||
|
|
||||||
|
return millisSinceLastUpdated <= daysInMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _logger: Logger = new Logger();
|
||||||
|
private readonly _statistics: Statistics | undefined;
|
||||||
|
private _operationsLeft = 0;
|
||||||
|
readonly client: InstanceType<typeof GitHub>;
|
||||||
|
readonly options: IIssuesProcessorOptions;
|
||||||
|
readonly staleIssues: Issue[] = [];
|
||||||
|
readonly closedIssues: Issue[] = [];
|
||||||
|
readonly deletedBranchIssues: Issue[] = [];
|
||||||
|
readonly removedLabelIssues: Issue[] = [];
|
||||||
|
|
||||||
|
constructor(options: IIssuesProcessorOptions) {
|
||||||
|
this.options = options;
|
||||||
|
this._operationsLeft = this.options.operationsPerRun;
|
||||||
|
this.client = getOctokit(this.options.repoToken);
|
||||||
|
|
||||||
|
if (this.options.debugOnly) {
|
||||||
|
this._logger.warning(
|
||||||
|
'Executing in debug mode. Debug output will be written but no issues will be processed.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.enableStatistics) {
|
||||||
|
this._statistics = new Statistics(this.options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processIssues(page = 1): Promise<number> {
|
||||||
|
// get the next batch of issues
|
||||||
|
const issues: Issue[] = await this.getIssues(page);
|
||||||
|
const actor: string = await this.getActor();
|
||||||
|
|
||||||
|
if (issues.length <= 0) {
|
||||||
|
this._logger.info('---');
|
||||||
|
this._statistics?.setOperationsLeft(this._operationsLeft).logStats();
|
||||||
|
this._logger.info('No more issues found to process. Exiting.');
|
||||||
|
|
||||||
|
return this._operationsLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const issue of issues.values()) {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
this._statistics?.incrementProcessedIssuesCount();
|
||||||
|
|
||||||
|
issueLogger.info(`Found this $$type last updated ${issue.updated_at}`);
|
||||||
|
|
||||||
|
// calculate string based messages for this issue
|
||||||
|
const staleMessage: string = issue.isPullRequest
|
||||||
|
? this.options.stalePrMessage
|
||||||
|
: this.options.staleIssueMessage;
|
||||||
|
const closeMessage: string = issue.isPullRequest
|
||||||
|
? this.options.closePrMessage
|
||||||
|
: this.options.closeIssueMessage;
|
||||||
|
const staleLabel: string = issue.isPullRequest
|
||||||
|
? this.options.stalePrLabel
|
||||||
|
: this.options.staleIssueLabel;
|
||||||
|
const closeLabel: string = issue.isPullRequest
|
||||||
|
? this.options.closePrLabel
|
||||||
|
: this.options.closeIssueLabel;
|
||||||
|
const skipMessage = issue.isPullRequest
|
||||||
|
? this.options.skipStalePrMessage
|
||||||
|
: this.options.skipStaleIssueMessage;
|
||||||
|
const daysBeforeStale: number = issue.isPullRequest
|
||||||
|
? this._getDaysBeforePrStale()
|
||||||
|
: this._getDaysBeforeIssueStale();
|
||||||
|
const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));
|
||||||
|
|
||||||
|
if (onlyLabels.length > 0) {
|
||||||
|
issueLogger.info(
|
||||||
|
`The option "onlyLabels" was specified to only processed the issues and pull requests with all those labels (${onlyLabels.length})`
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAllWhitelistedLabels: boolean = onlyLabels.every(
|
||||||
|
(label: Readonly<string>): boolean => {
|
||||||
|
return isLabeled(issue, label);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasAllWhitelistedLabels) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Skipping this $$type because it doesn't have all the required labels`
|
||||||
|
);
|
||||||
|
continue; // Don't process issues without all of the required labels
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
`All the required labels are present on this $$type. Continuing the process`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
`The option "onlyLabels" was not specified. Continuing the process for this $$type`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
issueLogger.info(`Days before $$type stale: ${daysBeforeStale}`);
|
||||||
|
|
||||||
|
const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale);
|
||||||
|
|
||||||
|
if (!staleMessage && shouldMarkAsStale) {
|
||||||
|
issueLogger.info(`Skipping $$type due to empty stale message`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issue.state === 'closed') {
|
||||||
|
issueLogger.info(`Skipping $$type because it is closed`);
|
||||||
|
continue; // don't process closed issues
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issue.locked) {
|
||||||
|
issueLogger.info(`Skipping $$type because it is locked`);
|
||||||
|
continue; // don't process locked issues
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to remove the close label when not close/locked issue or PR
|
||||||
|
await this._removeCloseLabel(issue, closeLabel);
|
||||||
|
|
||||||
|
if (this.options.startDate) {
|
||||||
|
const startDate: Date = new Date(this.options.startDate);
|
||||||
|
const createdAt: Date = new Date(issue.created_at);
|
||||||
|
|
||||||
|
issueLogger.info(
|
||||||
|
`A start date was specified for the ${getHumanizedDate(startDate)} (${
|
||||||
|
this.options.startDate
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Expecting that GitHub will always set a creation date on the issues and PRs
|
||||||
|
// But you never know!
|
||||||
|
if (!isValidDate(createdAt)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid issue field: "created_at". Expected a valid date`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
issueLogger.info(
|
||||||
|
`$$type created the ${getHumanizedDate(createdAt)} (${
|
||||||
|
issue.created_at
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDateMoreRecentThan(createdAt, startDate)) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Skipping $$type because it was created before the specified start date`
|
||||||
|
);
|
||||||
|
|
||||||
|
continue; // don't process issues which were created before the start date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`This $$type has a stale label`);
|
||||||
|
} else {
|
||||||
|
issueLogger.info(`This $$type hasn't a stale label`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptLabels: string[] = wordsToList(
|
||||||
|
issue.isPullRequest
|
||||||
|
? this.options.exemptPrLabels
|
||||||
|
: this.options.exemptIssueLabels
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
exemptLabels.some((exemptLabel: Readonly<string>): boolean =>
|
||||||
|
isLabeled(issue, exemptLabel)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`An exempt label was added after the stale label.`);
|
||||||
|
await this._removeStaleLabel(issue, staleLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
issueLogger.info(`Skipping $$type because it has an exempt label`);
|
||||||
|
continue; // don't process exempt issues
|
||||||
|
}
|
||||||
|
|
||||||
|
const anyOfLabels: string[] = wordsToList(this.options.anyOfLabels);
|
||||||
|
if (
|
||||||
|
anyOfLabels.length &&
|
||||||
|
!anyOfLabels.some((label: Readonly<string>): boolean =>
|
||||||
|
isLabeled(issue, label)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Skipping $$type because it does not have any of the required labels`
|
||||||
|
);
|
||||||
|
continue; // don't process issues without any of the required labels
|
||||||
|
}
|
||||||
|
|
||||||
|
const milestones: Milestones = new Milestones(this.options, issue);
|
||||||
|
|
||||||
|
if (milestones.shouldExemptMilestones()) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Skipping $$type because it has an exempted milestone`
|
||||||
|
);
|
||||||
|
continue; // don't process exempt milestones
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignees: Assignees = new Assignees(this.options, issue);
|
||||||
|
|
||||||
|
if (assignees.shouldExemptAssignees()) {
|
||||||
|
continue; // don't process exempt assignees
|
||||||
|
}
|
||||||
|
|
||||||
|
// should this issue be marked stale?
|
||||||
|
const shouldBeStale = !IssuesProcessor._updatedSince(
|
||||||
|
issue.updated_at,
|
||||||
|
daysBeforeStale
|
||||||
|
);
|
||||||
|
|
||||||
|
// determine if this issue needs to be marked stale first
|
||||||
|
if (!issue.isStale && shouldBeStale && shouldMarkAsStale) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Marking $$type stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
|
||||||
|
);
|
||||||
|
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||||
|
issue.isStale = true; // this issue is now considered stale
|
||||||
|
} else if (!issue.isStale) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Not marking as stale: shouldBeStale=${shouldBeStale}, shouldMarkAsStale=${shouldMarkAsStale}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the issue if it was marked stale
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`Found a stale $$type`);
|
||||||
|
await this._processStaleIssue(
|
||||||
|
issue,
|
||||||
|
staleLabel,
|
||||||
|
actor,
|
||||||
|
closeMessage,
|
||||||
|
closeLabel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._operationsLeft <= 0) {
|
||||||
|
this._logger.warning(
|
||||||
|
'Reached max number of operations to process. Exiting.'
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the next batch
|
||||||
|
return this.processIssues(page + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab comments for an issue since a given date
|
||||||
|
async listIssueComments(
|
||||||
|
issueNumber: number,
|
||||||
|
sinceDate: string
|
||||||
|
): Promise<IComment[]> {
|
||||||
|
// find any comments since date on the given issue
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementFetchedIssuesCommentsCount();
|
||||||
|
const comments = await this.client.issues.listComments({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
since: sinceDate
|
||||||
|
});
|
||||||
|
return comments.data;
|
||||||
|
} catch (error) {
|
||||||
|
this._logger.error(`List issue comments error: ${error.message}`);
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the actor from the GitHub token or context
|
||||||
|
async getActor(): Promise<string> {
|
||||||
|
let actor;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
actor = await this.client.users.getAuthenticated();
|
||||||
|
this._logger.info('Actor is from client')
|
||||||
|
} catch (error) {
|
||||||
|
this._logger.info('Actor is from context')
|
||||||
|
return context.actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actor.data.login;
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab issues from github in batches of 100
|
||||||
|
async getIssues(page: number): Promise<Issue[]> {
|
||||||
|
// generate type for response
|
||||||
|
const endpoint = this.client.issues.listForRepo;
|
||||||
|
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementFetchedIssuesCount();
|
||||||
|
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
|
||||||
|
{
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
state: 'open',
|
||||||
|
per_page: 100,
|
||||||
|
direction: this.options.ascending ? 'asc' : 'desc',
|
||||||
|
page
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return issueResult.data.map(
|
||||||
|
(issue: Readonly<IIssue>): Issue => new Issue(this.options, issue)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this._logger.error(`Get issues for repo error: ${error.message}`);
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the creation date of a given label on an issue (or nothing if no label existed)
|
||||||
|
///see https://developer.github.com/v3/activity/events/
|
||||||
|
async getLabelCreationDate(
|
||||||
|
issue: Issue,
|
||||||
|
label: string
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(`Checking for label on $$type`);
|
||||||
|
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementFetchedIssuesEventsCount();
|
||||||
|
const options = this.client.issues.listEvents.endpoint.merge({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
per_page: 100,
|
||||||
|
issue_number: issue.number
|
||||||
|
});
|
||||||
|
|
||||||
|
const events: IIssueEvent[] = await this.client.paginate(options);
|
||||||
|
const reversedEvents = events.reverse();
|
||||||
|
|
||||||
|
const staleLabeledEvent = reversedEvents.find(
|
||||||
|
event => event.event === 'labeled' && event.label.name === label
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!staleLabeledEvent) {
|
||||||
|
// Must be old rather than labeled
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return staleLabeledEvent.created_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle all of the stale issue logic when we find a stale issue
|
||||||
|
private async _processStaleIssue(
|
||||||
|
issue: Issue,
|
||||||
|
staleLabel: string,
|
||||||
|
actor: string,
|
||||||
|
closeMessage?: string,
|
||||||
|
closeLabel?: string
|
||||||
|
) {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
const markedStaleOn: string =
|
||||||
|
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
|
||||||
|
issueLogger.info(`$$type marked stale on: ${markedStaleOn}`);
|
||||||
|
|
||||||
|
const issueHasComments: boolean = await this._hasCommentsSince(
|
||||||
|
issue,
|
||||||
|
markedStaleOn,
|
||||||
|
actor
|
||||||
|
);
|
||||||
|
issueLogger.info(`$$type has been commented on: ${issueHasComments}`);
|
||||||
|
|
||||||
|
const isPr: boolean = isPullRequest(issue);
|
||||||
|
const daysBeforeClose: number = isPr
|
||||||
|
? this._getDaysBeforePrClose()
|
||||||
|
: this._getDaysBeforeIssueClose();
|
||||||
|
|
||||||
|
issueLogger.info(`Days before $$type close: ${daysBeforeClose}`);
|
||||||
|
|
||||||
|
const issueHasUpdate: boolean = IssuesProcessor._updatedSince(
|
||||||
|
issue.updated_at,
|
||||||
|
daysBeforeClose
|
||||||
|
);
|
||||||
|
issueLogger.info(`$$type has been updated: ${issueHasUpdate}`);
|
||||||
|
|
||||||
|
// should we un-stale this issue?
|
||||||
|
if (this.options.removeStaleWhenUpdated && issueHasComments) {
|
||||||
|
await this._removeStaleLabel(issue, staleLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now start closing logic
|
||||||
|
if (daysBeforeClose < 0) {
|
||||||
|
return; // nothing to do because we aren't closing stale issues
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!issueHasComments && !issueHasUpdate) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Closing $$type because it was last updated on ${issue.updated_at}`
|
||||||
|
);
|
||||||
|
await this._closeIssue(issue, closeMessage, closeLabel);
|
||||||
|
|
||||||
|
if (this.options.deleteBranch && issue.pull_request) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Deleting branch for as delete-branch option was specified`
|
||||||
|
);
|
||||||
|
await this._deleteBranch(issue);
|
||||||
|
this.deletedBranchIssues.push(issue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
`Stale $$type is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks to see if a given issue is still stale (has had activity on it)
|
||||||
|
private async _hasCommentsSince(
|
||||||
|
issue: Issue,
|
||||||
|
sinceDate: string,
|
||||||
|
actor: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(`Checking for comments on $$type since ${sinceDate}`);
|
||||||
|
|
||||||
|
if (!sinceDate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find any comments since the date
|
||||||
|
const comments = await this.listIssueComments(issue.number, sinceDate);
|
||||||
|
|
||||||
|
issueLogger.info('Not filtering for debug purposes')
|
||||||
|
const filteredComments = comments.filter(
|
||||||
|
comment => comment.user.type === 'User' && comment.user.login !== actor
|
||||||
|
);
|
||||||
|
|
||||||
|
issueLogger.info(
|
||||||
|
`Comments not made by actor or another bot: ${filteredComments.length}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// if there are any user comments returned
|
||||||
|
return filteredComments.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark an issue as stale with a comment and a label
|
||||||
|
private async _markStale(
|
||||||
|
issue: Issue,
|
||||||
|
staleMessage: string,
|
||||||
|
staleLabel: string,
|
||||||
|
skipMessage: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(`Marking $$type as stale`);
|
||||||
|
this.staleIssues.push(issue);
|
||||||
|
|
||||||
|
// if the issue is being marked stale, 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 (this.options.debugOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipMessage) {
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementAddedComment();
|
||||||
|
await this.client.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
body: staleMessage
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(`Error creating a comment: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementAddedLabel();
|
||||||
|
this._statistics?.incrementStaleIssuesCount();
|
||||||
|
await this.client.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
labels: [staleLabel]
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(`Error adding a label: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close an issue based on staleness
|
||||||
|
private async _closeIssue(
|
||||||
|
issue: Issue,
|
||||||
|
closeMessage?: string,
|
||||||
|
closeLabel?: string
|
||||||
|
): Promise<void> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(`Closing $$type for being stale`);
|
||||||
|
this.closedIssues.push(issue);
|
||||||
|
|
||||||
|
if (this.options.debugOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeMessage) {
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementAddedComment();
|
||||||
|
await this.client.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
body: closeMessage
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(`Error creating a comment: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeLabel) {
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementAddedLabel();
|
||||||
|
await this.client.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
labels: [closeLabel]
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(`Error adding a label: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementClosedIssuesCount();
|
||||||
|
await this.client.issues.update({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
state: 'closed'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(`Error updating this $$type: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getPullRequest(
|
||||||
|
issue: Issue
|
||||||
|
): Promise<IPullRequest | undefined | void> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
if (this.options.debugOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementFetchedPullRequestsCount();
|
||||||
|
const pullRequest = await this.client.pulls.get({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
pull_number: issue.number
|
||||||
|
});
|
||||||
|
|
||||||
|
return pullRequest.data;
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(`Error getting this $$type: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the branch on closed pull request
|
||||||
|
private async _deleteBranch(issue: Issue): Promise<void> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(`Delete branch from closed $$type - ${issue.title}`);
|
||||||
|
|
||||||
|
const pullRequest = await this._getPullRequest(issue);
|
||||||
|
|
||||||
|
if (!pullRequest) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Not deleting branch as pull request not found for this $$type`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.debugOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const branch = pullRequest.head.ref;
|
||||||
|
issueLogger.info(`Deleting branch ${branch} from closed $$type`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementDeletedBranchesCount();
|
||||||
|
await this.client.git.deleteRef({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: `heads/${branch}`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(
|
||||||
|
`Error deleting branch ${branch} from $$type: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a label from an issue
|
||||||
|
private async _removeLabel(issue: Issue, label: string): Promise<void> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(`Removing label "${label}" from $$type`);
|
||||||
|
this.removedLabelIssues.push(issue);
|
||||||
|
|
||||||
|
// @todo remove the debug only to be able to test the code below
|
||||||
|
if (this.options.debugOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._operationsLeft -= 1;
|
||||||
|
this._statistics?.incrementDeletedLabelsCount();
|
||||||
|
await this.client.issues.removeLabel({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
name: label
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
issueLogger.error(`Error removing a label: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDaysBeforeIssueStale(): number {
|
||||||
|
return isNaN(this.options.daysBeforeIssueStale)
|
||||||
|
? this.options.daysBeforeStale
|
||||||
|
: this.options.daysBeforeIssueStale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDaysBeforePrStale(): number {
|
||||||
|
return isNaN(this.options.daysBeforePrStale)
|
||||||
|
? this.options.daysBeforeStale
|
||||||
|
: this.options.daysBeforePrStale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDaysBeforeIssueClose(): number {
|
||||||
|
return isNaN(this.options.daysBeforeIssueClose)
|
||||||
|
? this.options.daysBeforeClose
|
||||||
|
: this.options.daysBeforeIssueClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDaysBeforePrClose(): number {
|
||||||
|
return isNaN(this.options.daysBeforePrClose)
|
||||||
|
? this.options.daysBeforeClose
|
||||||
|
: this.options.daysBeforePrClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getOnlyLabels(issue: Issue): string {
|
||||||
|
if (issue.isPullRequest) {
|
||||||
|
if (this.options.onlyPrLabels !== '') {
|
||||||
|
return this.options.onlyPrLabels;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.options.onlyIssueLabels !== '') {
|
||||||
|
return this.options.onlyIssueLabels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.options.onlyLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeStaleLabel(
|
||||||
|
issue: Issue,
|
||||||
|
staleLabel: Readonly<string>
|
||||||
|
): Promise<void> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(
|
||||||
|
`The $$type is no longer stale. Removing the stale label...`
|
||||||
|
);
|
||||||
|
|
||||||
|
await this._removeLabel(issue, staleLabel);
|
||||||
|
this._statistics?.incrementUndoStaleIssuesCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeCloseLabel(
|
||||||
|
issue: Issue,
|
||||||
|
closeLabel: Readonly<string | undefined>
|
||||||
|
): Promise<void> {
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
issueLogger.info(
|
||||||
|
`The $$type is not closed nor locked. Trying to remove the close label...`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!closeLabel) {
|
||||||
|
issueLogger.info(`There is no close label on this $$type. Skip`);
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLabeled(issue, closeLabel)) {
|
||||||
|
issueLogger.info(
|
||||||
|
`The $$type has a close label "${closeLabel}". Removing the close label...`
|
||||||
|
);
|
||||||
|
|
||||||
|
await this._removeLabel(issue, closeLabel);
|
||||||
|
this._statistics?.incrementDeletedCloseLabelsCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
167
src/classes/loggers/issue-logger.spec.ts
Normal file
167
src/classes/loggers/issue-logger.spec.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import {DefaultProcessorOptions} from '../../../__tests__/constants/default-processor-options';
|
||||||
|
import {generateIIssue} from '../../../__tests__/functions/generate-iissue';
|
||||||
|
import {Issue} from '../issue';
|
||||||
|
import {IssueLogger} from './issue-logger';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
|
||||||
|
describe('IssueLogger', (): void => {
|
||||||
|
let issue: Issue;
|
||||||
|
let issueLogger: IssueLogger;
|
||||||
|
let message: string;
|
||||||
|
|
||||||
|
let coreWarningSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
describe('warning()', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
message = 'dummy-message';
|
||||||
|
issue = new Issue(
|
||||||
|
DefaultProcessorOptions,
|
||||||
|
generateIIssue({
|
||||||
|
number: 8
|
||||||
|
})
|
||||||
|
);
|
||||||
|
issueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log a warning with the given message and with the issue number as prefix', (): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
|
||||||
|
issueLogger.warning(message);
|
||||||
|
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledWith('[#8] dummy-message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('info()', (): void => {
|
||||||
|
let coreInfoSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
message = 'dummy-message';
|
||||||
|
issue = new Issue(
|
||||||
|
DefaultProcessorOptions,
|
||||||
|
generateIIssue({
|
||||||
|
number: 8
|
||||||
|
})
|
||||||
|
);
|
||||||
|
issueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
coreInfoSpy = jest.spyOn(core, 'info').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log an information with the given message and with the issue number as prefix', (): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
|
||||||
|
issueLogger.info(message);
|
||||||
|
|
||||||
|
expect(coreInfoSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreInfoSpy).toHaveBeenCalledWith('[#8] dummy-message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('error()', (): void => {
|
||||||
|
let coreErrorSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
message = 'dummy-message';
|
||||||
|
issue = new Issue(
|
||||||
|
DefaultProcessorOptions,
|
||||||
|
generateIIssue({
|
||||||
|
number: 8
|
||||||
|
})
|
||||||
|
);
|
||||||
|
issueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
|
coreErrorSpy = jest.spyOn(core, 'error').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log an error with the given message and with the issue number as prefix', (): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
|
||||||
|
issueLogger.error(message);
|
||||||
|
|
||||||
|
expect(coreErrorSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreErrorSpy).toHaveBeenCalledWith('[#8] dummy-message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prefix the message with the issue number', (): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
message = 'dummy-message';
|
||||||
|
issue = new Issue(
|
||||||
|
DefaultProcessorOptions,
|
||||||
|
generateIIssue({
|
||||||
|
number: 123
|
||||||
|
})
|
||||||
|
);
|
||||||
|
issueLogger = new IssueLogger(issue);
|
||||||
|
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||||
|
|
||||||
|
issueLogger.warning(message);
|
||||||
|
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledWith('[#123] dummy-message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
pull_request | replacement
|
||||||
|
${{key: 'value'}} | ${'pull request'}
|
||||||
|
${{}} | ${'pull request'}
|
||||||
|
${null} | ${'issue'}
|
||||||
|
${undefined} | ${'issue'}
|
||||||
|
`(
|
||||||
|
'should replace the special tokens "$$type" with the corresponding type',
|
||||||
|
({pull_request, replacement}): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
message = 'The $$type will stale! $$type will soon be closed!';
|
||||||
|
issue = new Issue(
|
||||||
|
DefaultProcessorOptions,
|
||||||
|
generateIIssue({
|
||||||
|
number: 8,
|
||||||
|
pull_request
|
||||||
|
})
|
||||||
|
);
|
||||||
|
issueLogger = new IssueLogger(issue);
|
||||||
|
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||||
|
|
||||||
|
issueLogger.warning(message);
|
||||||
|
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledWith(
|
||||||
|
`[#8] The ${replacement} will stale! ${replacement} will soon be closed!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
pull_request | replacement
|
||||||
|
${{key: 'value'}} | ${'Pull request'}
|
||||||
|
${{}} | ${'Pull request'}
|
||||||
|
${null} | ${'Issue'}
|
||||||
|
${undefined} | ${'Issue'}
|
||||||
|
`(
|
||||||
|
'should replace the special token "$$type" with the corresponding type with first letter as uppercase',
|
||||||
|
({pull_request, replacement}): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
message = '$$type will stale';
|
||||||
|
issue = new Issue(
|
||||||
|
DefaultProcessorOptions,
|
||||||
|
generateIIssue({
|
||||||
|
number: 8,
|
||||||
|
pull_request
|
||||||
|
})
|
||||||
|
);
|
||||||
|
issueLogger = new IssueLogger(issue);
|
||||||
|
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||||
|
|
||||||
|
issueLogger.warning(message);
|
||||||
|
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledWith(
|
||||||
|
`[#8] ${replacement} will stale`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
64
src/classes/loggers/issue-logger.ts
Normal file
64
src/classes/loggers/issue-logger.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import * as core from '@actions/core';
|
||||||
|
import {Issue} from '../issue';
|
||||||
|
import {Logger} from './logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Each log will prefix the message with the issue number
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* warning('No stale') => "[#123] No stale"
|
||||||
|
*
|
||||||
|
* Each log method can have special tokens:
|
||||||
|
* - $$type => will replace this by either "pull request" or "issue" depending of the type of issue
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* warning('The $$type will stale') => "The pull request will stale"
|
||||||
|
*/
|
||||||
|
export class IssueLogger implements Logger {
|
||||||
|
private readonly _issue: Issue;
|
||||||
|
|
||||||
|
constructor(issue: Issue) {
|
||||||
|
this._issue = issue;
|
||||||
|
}
|
||||||
|
|
||||||
|
warning(message: Readonly<string>): void {
|
||||||
|
core.warning(this._format(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
info(message: Readonly<string>): void {
|
||||||
|
core.info(this._format(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message: Readonly<string>): void {
|
||||||
|
core.error(this._format(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _replaceTokens(message: Readonly<string>): string {
|
||||||
|
return this._replaceTypeToken(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _replaceTypeToken(message: Readonly<string>): string {
|
||||||
|
return message
|
||||||
|
.replace(
|
||||||
|
/^\$\$type/,
|
||||||
|
this._issue.isPullRequest ? 'Pull request' : 'Issue'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
/\$\$type/g,
|
||||||
|
this._issue.isPullRequest ? 'pull request' : 'issue'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _prefixWithIssueNumber(message: Readonly<string>): string {
|
||||||
|
return `[#${this._getIssueNumber()}] ${message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getIssueNumber(): number {
|
||||||
|
return this._issue.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _format(message: Readonly<string>): string {
|
||||||
|
return this._prefixWithIssueNumber(this._replaceTokens(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/classes/loggers/logger.spec.ts
Normal file
73
src/classes/loggers/logger.spec.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import {Logger} from './logger';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
|
||||||
|
describe('Logger', (): void => {
|
||||||
|
let logger: Logger;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
logger = new Logger();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('warning()', (): void => {
|
||||||
|
let message: string;
|
||||||
|
|
||||||
|
let coreWarningSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
message = 'dummy-message';
|
||||||
|
|
||||||
|
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log a warning with the given message', (): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
|
||||||
|
logger.warning(message);
|
||||||
|
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreWarningSpy).toHaveBeenCalledWith('dummy-message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('info()', (): void => {
|
||||||
|
let message: string;
|
||||||
|
|
||||||
|
let coreInfoSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
message = 'dummy-message';
|
||||||
|
|
||||||
|
coreInfoSpy = jest.spyOn(core, 'info').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log an information with the given message', (): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
|
||||||
|
logger.info(message);
|
||||||
|
|
||||||
|
expect(coreInfoSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreInfoSpy).toHaveBeenCalledWith('dummy-message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('error()', (): void => {
|
||||||
|
let message: string;
|
||||||
|
|
||||||
|
let coreErrorSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
message = 'dummy-message';
|
||||||
|
|
||||||
|
coreErrorSpy = jest.spyOn(core, 'error').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log an error with the given message', (): void => {
|
||||||
|
expect.assertions(2);
|
||||||
|
|
||||||
|
logger.error(message);
|
||||||
|
|
||||||
|
expect(coreErrorSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(coreErrorSpy).toHaveBeenCalledWith('dummy-message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
15
src/classes/loggers/logger.ts
Normal file
15
src/classes/loggers/logger.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as core from '@actions/core';
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
warning(message: Readonly<string>): void {
|
||||||
|
core.warning(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
info(message: Readonly<string>): void {
|
||||||
|
core.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message: Readonly<string>): void {
|
||||||
|
core.error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
558
src/classes/milestones.spec.ts
Normal file
558
src/classes/milestones.spec.ts
Normal file
@@ -0,0 +1,558 @@
|
|||||||
|
import {DefaultProcessorOptions} from '../../__tests__/constants/default-processor-options';
|
||||||
|
import {generateIIssue} from '../../__tests__/functions/generate-iissue';
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
import {Milestones} from './milestones';
|
||||||
|
|
||||||
|
describe('Milestones', (): void => {
|
||||||
|
let milestones: Milestones;
|
||||||
|
let optionsInterface: IIssuesProcessorOptions;
|
||||||
|
let issue: Issue;
|
||||||
|
let issueInterface: IIssue;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface = {...DefaultProcessorOptions};
|
||||||
|
issueInterface = generateIIssue();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('shouldExemptMilestones()', (): void => {
|
||||||
|
describe('when the given issue is not a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones =
|
||||||
|
'dummy-exempt-issue-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-issue-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = 'dummy-exempt-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones =
|
||||||
|
'dummy-exempt-issue-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-issue-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue is a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-pr-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = 'dummy-exempt-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-pr-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
93
src/classes/milestones.ts
Normal file
93
src/classes/milestones.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import deburr from 'lodash.deburr';
|
||||||
|
import {wordsToList} from '../functions/words-to-list';
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
|
||||||
|
type CleanMilestone = string;
|
||||||
|
|
||||||
|
export class Milestones {
|
||||||
|
private static _cleanMilestone(milestone: Readonly<string>): CleanMilestone {
|
||||||
|
return deburr(milestone.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _options: IIssuesProcessorOptions;
|
||||||
|
private readonly _issue: Issue;
|
||||||
|
|
||||||
|
constructor(options: Readonly<IIssuesProcessorOptions>, issue: Issue) {
|
||||||
|
this._options = options;
|
||||||
|
this._issue = issue;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldExemptMilestones(): boolean {
|
||||||
|
if (this._shouldExemptAllMilestones()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptMilestones: string[] = this._getExemptMilestones();
|
||||||
|
|
||||||
|
return exemptMilestones.some((exemptMilestone: Readonly<string>): boolean =>
|
||||||
|
this._hasMilestone(exemptMilestone)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptMilestones(): string[] {
|
||||||
|
return wordsToList(
|
||||||
|
this._issue.isPullRequest
|
||||||
|
? this._getExemptPullRequestMilestones()
|
||||||
|
: this._getExemptIssueMilestones()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptIssueMilestones(): string {
|
||||||
|
return this._options.exemptIssueMilestones !== ''
|
||||||
|
? this._options.exemptIssueMilestones
|
||||||
|
: this._options.exemptMilestones;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptPullRequestMilestones(): string {
|
||||||
|
return this._options.exemptPrMilestones !== ''
|
||||||
|
? this._options.exemptPrMilestones
|
||||||
|
: this._options.exemptMilestones;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hasMilestone(milestone: Readonly<string>): boolean {
|
||||||
|
if (!this._issue.milestone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
Milestones._cleanMilestone(milestone) ===
|
||||||
|
Milestones._cleanMilestone(this._issue.milestone.title)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldExemptAllMilestones(): boolean {
|
||||||
|
if (this._issue.milestone) {
|
||||||
|
return this._issue.isPullRequest
|
||||||
|
? this._shouldExemptAllPullRequestMilestones()
|
||||||
|
: this._shouldExemptAllIssueMilestones();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldExemptAllIssueMilestones(): boolean {
|
||||||
|
if (this._options.exemptAllIssueMilestones === true) {
|
||||||
|
return true;
|
||||||
|
} else if (this._options.exemptAllIssueMilestones === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._options.exemptAllMilestones;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldExemptAllPullRequestMilestones(): boolean {
|
||||||
|
if (this._options.exemptAllPrMilestones === true) {
|
||||||
|
return true;
|
||||||
|
} else if (this._options.exemptAllPrMilestones === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._options.exemptAllMilestones;
|
||||||
|
}
|
||||||
|
}
|
||||||
200
src/classes/statistics.ts
Normal file
200
src/classes/statistics.ts
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
import {Logger} from './loggers/logger';
|
||||||
|
|
||||||
|
export class Statistics {
|
||||||
|
private readonly _logger: Logger = new Logger();
|
||||||
|
private readonly _options: IIssuesProcessorOptions;
|
||||||
|
private _processedIssuesCount = 0;
|
||||||
|
private _staleIssuesCount = 0;
|
||||||
|
private _undoStaleIssuesCount = 0;
|
||||||
|
private _operationsCount = 0;
|
||||||
|
private _closedIssuesCount = 0;
|
||||||
|
private _deletedLabelsCount = 0;
|
||||||
|
private _deletedCloseLabelsCount = 0;
|
||||||
|
private _deletedBranchesCount = 0;
|
||||||
|
private _addedLabelsCount = 0;
|
||||||
|
private _addedCommentsCount = 0;
|
||||||
|
private _fetchedIssuesCount = 0;
|
||||||
|
private _fetchedIssuesEventsCount = 0;
|
||||||
|
private _fetchedIssuesCommentsCount = 0;
|
||||||
|
private _fetchedPullRequestsCount = 0;
|
||||||
|
|
||||||
|
constructor(options: IIssuesProcessorOptions) {
|
||||||
|
this._options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementProcessedIssuesCount(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._processedIssuesCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementStaleIssuesCount(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._staleIssuesCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementUndoStaleIssuesCount(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._undoStaleIssuesCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOperationsLeft(operationsLeft: Readonly<number>): Statistics {
|
||||||
|
this._operationsCount = this._options.operationsPerRun - operationsLeft;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementClosedIssuesCount(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._closedIssuesCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementDeletedLabelsCount(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._deletedLabelsCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementDeletedCloseLabelsCount(
|
||||||
|
increment: Readonly<number> = 1
|
||||||
|
): Statistics {
|
||||||
|
this._deletedCloseLabelsCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementDeletedBranchesCount(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._deletedBranchesCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementAddedLabel(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._addedLabelsCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementAddedComment(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._addedCommentsCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementFetchedIssuesCount(increment: Readonly<number> = 1): Statistics {
|
||||||
|
this._fetchedIssuesCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementFetchedIssuesEventsCount(
|
||||||
|
increment: Readonly<number> = 1
|
||||||
|
): Statistics {
|
||||||
|
this._fetchedIssuesEventsCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementFetchedIssuesCommentsCount(
|
||||||
|
increment: Readonly<number> = 1
|
||||||
|
): Statistics {
|
||||||
|
this._fetchedIssuesCommentsCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementFetchedPullRequestsCount(
|
||||||
|
increment: Readonly<number> = 1
|
||||||
|
): Statistics {
|
||||||
|
this._fetchedPullRequestsCount += increment;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
logStats(): Statistics {
|
||||||
|
this._logger.info('Statistics');
|
||||||
|
this._logProcessedIssuesCount();
|
||||||
|
this._logStaleIssuesCount();
|
||||||
|
this._logUndoStaleIssuesCount();
|
||||||
|
this._logOperationsCount();
|
||||||
|
this._logClosedIssuesCount();
|
||||||
|
this._logDeletedLabelsCount();
|
||||||
|
this._logDeletedCloseLabelsCount();
|
||||||
|
this._logDeletedBranchesCount();
|
||||||
|
this._logAddedLabelsCount();
|
||||||
|
this._logAddedCommentsCount();
|
||||||
|
this._logFetchedIssuesCount();
|
||||||
|
this._logFetchedIssuesEventsCount();
|
||||||
|
this._logFetchedIssuesCommentsCount();
|
||||||
|
this._logFetchedPullRequestsCount();
|
||||||
|
this._logger.info('---');
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logProcessedIssuesCount(): void {
|
||||||
|
this._logCount('Processed issues/PRs', this._processedIssuesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logStaleIssuesCount(): void {
|
||||||
|
this._logCount('New stale issues/PRs', this._staleIssuesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logUndoStaleIssuesCount(): void {
|
||||||
|
this._logCount('No longer stale issues/PRs', this._undoStaleIssuesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logOperationsCount(): void {
|
||||||
|
this._logCount('Operations performed', this._operationsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logClosedIssuesCount(): void {
|
||||||
|
this._logCount('Closed issues', this._closedIssuesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logDeletedLabelsCount(): void {
|
||||||
|
this._logCount('Deleted labels', this._deletedLabelsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logDeletedCloseLabelsCount(): void {
|
||||||
|
this._logCount('Deleted close labels', this._deletedCloseLabelsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logDeletedBranchesCount(): void {
|
||||||
|
this._logCount('Deleted branches', this._deletedBranchesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logAddedLabelsCount(): void {
|
||||||
|
this._logCount('Added labels', this._addedLabelsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logAddedCommentsCount(): void {
|
||||||
|
this._logCount('Added comments', this._addedCommentsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logFetchedIssuesCount(): void {
|
||||||
|
this._logCount('Fetched issues', this._fetchedIssuesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logFetchedIssuesEventsCount(): void {
|
||||||
|
this._logCount('Fetched issues events', this._fetchedIssuesEventsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logFetchedIssuesCommentsCount(): void {
|
||||||
|
this._logCount('Fetched issues comments', this._fetchedIssuesCommentsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logFetchedPullRequestsCount(): void {
|
||||||
|
this._logCount('Fetched pull requests', this._fetchedPullRequestsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logCount(name: Readonly<string>, count: Readonly<number>): void {
|
||||||
|
if (count > 0) {
|
||||||
|
this._logger.info(`${name}: ${count}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/enums/issue-type.ts
Normal file
4
src/enums/issue-type.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum IssueType {
|
||||||
|
Issue = 'issue',
|
||||||
|
PullRequest = 'pr'
|
||||||
|
}
|
||||||
33
src/functions/dates/get-humanized-date.spec.ts
Normal file
33
src/functions/dates/get-humanized-date.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import {getHumanizedDate} from './get-humanized-date';
|
||||||
|
|
||||||
|
describe('getHumanizedDate()', (): void => {
|
||||||
|
let date: Date;
|
||||||
|
|
||||||
|
describe('when the given date is the 1st of april 2020', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date(2020, 3, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the date formatted as DD-MM-YYYY', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = getHumanizedDate(date);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual('01-04-2020');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given date is the 18st of december 2020', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date(2020, 11, 18);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the date formatted as DD-MM-YYYY', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = getHumanizedDate(date);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual('18-12-2020');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
17
src/functions/dates/get-humanized-date.ts
Normal file
17
src/functions/dates/get-humanized-date.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {HumanizedDate} from '../../types/humanized-date';
|
||||||
|
|
||||||
|
export function getHumanizedDate(date: Readonly<Date>): HumanizedDate {
|
||||||
|
const year: number = date.getFullYear();
|
||||||
|
let month = `${date.getMonth() + 1}`;
|
||||||
|
let day = `${date.getDate()}`;
|
||||||
|
|
||||||
|
if (month.length < 2) {
|
||||||
|
month = `0${month}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day.length < 2) {
|
||||||
|
day = `0${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [day, month, year].join('-');
|
||||||
|
}
|
||||||
51
src/functions/dates/is-date-more-recent-than.spec.ts
Normal file
51
src/functions/dates/is-date-more-recent-than.spec.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import {isDateMoreRecentThan} from './is-date-more-recent-than';
|
||||||
|
|
||||||
|
describe('isDateMoreRecentThan()', (): void => {
|
||||||
|
let date: Date;
|
||||||
|
let comparedDate: Date;
|
||||||
|
|
||||||
|
describe('when the given date is older than the compared date', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date(2020, 0, 20);
|
||||||
|
comparedDate = new Date(2021, 0, 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isDateMoreRecentThan(date, comparedDate);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given date is equal to the compared date', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date(2020, 0, 20);
|
||||||
|
comparedDate = new Date(2020, 0, 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isDateMoreRecentThan(date, comparedDate);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given date is more recent than the compared date', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date(2021, 0, 20);
|
||||||
|
comparedDate = new Date(2020, 0, 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isDateMoreRecentThan(date, comparedDate);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
6
src/functions/dates/is-date-more-recent-than.ts
Normal file
6
src/functions/dates/is-date-more-recent-than.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function isDateMoreRecentThan(
|
||||||
|
date: Readonly<Date>,
|
||||||
|
comparedDate: Readonly<Date>
|
||||||
|
): boolean {
|
||||||
|
return date > comparedDate;
|
||||||
|
}
|
||||||
61
src/functions/dates/is-valid-date.spec.ts
Normal file
61
src/functions/dates/is-valid-date.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import {isValidDate} from './is-valid-date';
|
||||||
|
|
||||||
|
describe('isValidDate()', (): void => {
|
||||||
|
let date: Date;
|
||||||
|
|
||||||
|
describe('when the given date is an invalid date', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date('16-04-1994');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isValidDate(date);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given date is a new date', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isValidDate(date);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given date is an ISO and valid date', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date('2011-04-22T13:33:48Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isValidDate(date);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given date is an ISO with ms and valid date', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
date = new Date('2011-10-05T14:48:00.000Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isValidDate(date);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
18
src/functions/dates/is-valid-date.ts
Normal file
18
src/functions/dates/is-valid-date.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Check if a date is valid
|
||||||
|
*
|
||||||
|
* @see
|
||||||
|
* https://stackoverflow.com/a/1353711/4440414
|
||||||
|
*
|
||||||
|
* @param {Readonly<Date>} date The date to check
|
||||||
|
*
|
||||||
|
* @returns {boolean} true when the given date is valid
|
||||||
|
*/
|
||||||
|
export function isValidDate(date: Readonly<Date>): boolean {
|
||||||
|
if (Object.prototype.toString.call(date) === '[object Date]') {
|
||||||
|
return !isNaN(date.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
187
src/functions/is-labeled.spec.ts
Normal file
187
src/functions/is-labeled.spec.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import {Issue} from '../classes/issue';
|
||||||
|
import {isLabeled} from './is-labeled';
|
||||||
|
|
||||||
|
describe('isLabeled()', (): void => {
|
||||||
|
let issue: Issue;
|
||||||
|
let label: string;
|
||||||
|
|
||||||
|
describe('when the given issue contains no label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = ({
|
||||||
|
labels: []
|
||||||
|
} as unknown) as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given label is a simple label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'label';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains a simple label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
name: 'label'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given label is a simple label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'label';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains a kebab case label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
name: 'kebab-case-label'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given label is a kebab case label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'kebab-case-label';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains a multiple word label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
name: 'label like a sentence'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given label is a multiple word label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'label like a sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains a multiple word label with %20 spaces', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
name: 'label%20like%20a%20sentence'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given label is a multiple word label with %20 spaces', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'label%20like%20a%20sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains a label wih diacritical marks', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
name: 'déjà vu'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains a label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'deja vu';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains an uppercase label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'DEJA VU';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains a label wih diacritical marks', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
label = 'déjà vu';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isLabeled(issue, label);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/functions/is-labeled.ts
Normal file
26
src/functions/is-labeled.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import deburr from 'lodash.deburr';
|
||||||
|
import {Issue} from '../classes/issue';
|
||||||
|
import {ILabel} from '../interfaces/label';
|
||||||
|
import {CleanLabel} from '../types/clean-label';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Check if the given label is listed as a label of the given issue
|
||||||
|
*
|
||||||
|
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
|
||||||
|
* @param {Readonly<string>} label The label to check the presence with
|
||||||
|
*
|
||||||
|
* @return {boolean} Return true when the given label is also in the given issue labels
|
||||||
|
*/
|
||||||
|
export function isLabeled(
|
||||||
|
issue: Readonly<Issue>,
|
||||||
|
label: Readonly<string>
|
||||||
|
): boolean {
|
||||||
|
return !!issue.labels.find((issueLabel: Readonly<ILabel>): boolean => {
|
||||||
|
return cleanLabel(label) === cleanLabel(issueLabel.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanLabel(label: Readonly<string>): CleanLabel {
|
||||||
|
return deburr(label.toLowerCase());
|
||||||
|
}
|
||||||
57
src/functions/is-pull-request.spec.ts
Normal file
57
src/functions/is-pull-request.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import {Issue} from '../classes/issue';
|
||||||
|
import {isPullRequest} from './is-pull-request';
|
||||||
|
|
||||||
|
describe('isPullRequest()', (): void => {
|
||||||
|
let issue: Issue;
|
||||||
|
|
||||||
|
describe('when the given issue has an undefined pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
pull_request: undefined
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isPullRequest(issue);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue has a null pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
pull_request: null
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isPullRequest(issue);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([{}, true])(
|
||||||
|
'when the given issue has pull request',
|
||||||
|
(value): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issue = {
|
||||||
|
pull_request: value
|
||||||
|
} as Issue;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = isPullRequest(issue);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
5
src/functions/is-pull-request.ts
Normal file
5
src/functions/is-pull-request.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import {Issue} from '../classes/issue';
|
||||||
|
|
||||||
|
export function isPullRequest(issue: Readonly<Issue>): boolean {
|
||||||
|
return !!issue.pull_request;
|
||||||
|
}
|
||||||
47
src/functions/should-mark-when-stale.spec.ts
Normal file
47
src/functions/should-mark-when-stale.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import {shouldMarkWhenStale} from './should-mark-when-stale';
|
||||||
|
|
||||||
|
describe('shouldMarkWhenStale()', (): void => {
|
||||||
|
let daysBeforeStale: number;
|
||||||
|
|
||||||
|
describe('when the given number of days indicate that it should be stalled', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
daysBeforeStale = -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = shouldMarkWhenStale(daysBeforeStale);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given number of days indicate that it should be stalled today', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
daysBeforeStale = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = shouldMarkWhenStale(daysBeforeStale);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given number of days indicate that it should be stalled tomorrow', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
daysBeforeStale = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = shouldMarkWhenStale(daysBeforeStale);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
5
src/functions/should-mark-when-stale.ts
Normal file
5
src/functions/should-mark-when-stale.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export function shouldMarkWhenStale(
|
||||||
|
daysBeforeStale: Readonly<number>
|
||||||
|
): boolean {
|
||||||
|
return daysBeforeStale >= 0;
|
||||||
|
}
|
||||||
137
src/functions/words-to-list.spec.ts
Normal file
137
src/functions/words-to-list.spec.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import {wordsToList} from './words-to-list';
|
||||||
|
|
||||||
|
describe('wordsToList()', (): void => {
|
||||||
|
let words: string;
|
||||||
|
|
||||||
|
describe('when the given words is empty', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty list of words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a simple word', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a word with extra spaces before and after', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = ' word ';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word and remove all spaces before and after', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a kebab case word', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'kebab-case-word';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['kebab-case-word']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is two kebab case words separated with a comma', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'kebab-case-word-1,kebab-case-word-2';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of two words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['kebab-case-word-1', 'kebab-case-word-2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a multiple word word', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word like a sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word like a sentence']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is two multiple word words separated with a comma', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word like a sentence, another word like a sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of two words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([
|
||||||
|
'word like a sentence',
|
||||||
|
'another word like a sentence'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a multiple word word with %20 spaces', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word%20like%20a%20sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word%20like%20a%20sentence']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is two multiple word words with %20 spaces separated with a comma', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word%20like%20a%20sentence,another%20word%20like%20a%20sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of two words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([
|
||||||
|
'word%20like%20a%20sentence',
|
||||||
|
'another%20word%20like%20a%20sentence'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
23
src/functions/words-to-list.ts
Normal file
23
src/functions/words-to-list.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Transform a string of comma separated words
|
||||||
|
* to an array of words
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* wordsToList('label') => ['label']
|
||||||
|
* wordsToList('label,label') => ['label', 'label']
|
||||||
|
* wordsToList('kebab-label') => ['kebab-label']
|
||||||
|
* wordsToList('kebab%20label') => ['kebab%20label']
|
||||||
|
* wordsToList('label with words') => ['label with words']
|
||||||
|
*
|
||||||
|
* @param {Readonly<string>} words A string of comma separated words
|
||||||
|
*
|
||||||
|
* @return {string[]} A list of words
|
||||||
|
*/
|
||||||
|
export function wordsToList(words: Readonly<string>): string[] {
|
||||||
|
if (!words.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return words.split(',').map((word: Readonly<string>): string => word.trim());
|
||||||
|
}
|
||||||
3
src/interfaces/assignee.ts
Normal file
3
src/interfaces/assignee.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface IAssignee {
|
||||||
|
login: string;
|
||||||
|
}
|
||||||
5
src/interfaces/comment.ts
Normal file
5
src/interfaces/comment.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import {IUser} from './user';
|
||||||
|
|
||||||
|
export interface IComment {
|
||||||
|
user: IUser;
|
||||||
|
}
|
||||||
7
src/interfaces/issue-event.ts
Normal file
7
src/interfaces/issue-event.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import {ILabel} from './label';
|
||||||
|
|
||||||
|
export interface IIssueEvent {
|
||||||
|
created_at: string;
|
||||||
|
event: string;
|
||||||
|
label: ILabel;
|
||||||
|
}
|
||||||
17
src/interfaces/issue.ts
Normal file
17
src/interfaces/issue.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {IsoDateString} from '../types/iso-date-string';
|
||||||
|
import {IAssignee} from './assignee';
|
||||||
|
import {ILabel} from './label';
|
||||||
|
import {IMilestone} from './milestone';
|
||||||
|
|
||||||
|
export interface IIssue {
|
||||||
|
title: string;
|
||||||
|
number: number;
|
||||||
|
created_at: IsoDateString;
|
||||||
|
updated_at: IsoDateString;
|
||||||
|
labels: ILabel[];
|
||||||
|
pull_request: Object | null | undefined;
|
||||||
|
state: string;
|
||||||
|
locked: boolean;
|
||||||
|
milestone: IMilestone | undefined;
|
||||||
|
assignees: IAssignee[];
|
||||||
|
}
|
||||||
46
src/interfaces/issues-processor-options.ts
Normal file
46
src/interfaces/issues-processor-options.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import {IsoOrRfcDateString} from '../types/iso-or-rfc-date-string';
|
||||||
|
|
||||||
|
export interface IIssuesProcessorOptions {
|
||||||
|
repoToken: string;
|
||||||
|
staleIssueMessage: string;
|
||||||
|
stalePrMessage: string;
|
||||||
|
closeIssueMessage: string;
|
||||||
|
closePrMessage: string;
|
||||||
|
daysBeforeStale: number;
|
||||||
|
daysBeforeIssueStale: number; // Could be NaN
|
||||||
|
daysBeforePrStale: number; // Could be NaN
|
||||||
|
daysBeforeClose: number;
|
||||||
|
daysBeforeIssueClose: number; // Could be NaN
|
||||||
|
daysBeforePrClose: number; // Could be NaN
|
||||||
|
staleIssueLabel: string;
|
||||||
|
closeIssueLabel: string;
|
||||||
|
exemptIssueLabels: string;
|
||||||
|
stalePrLabel: string;
|
||||||
|
closePrLabel: string;
|
||||||
|
exemptPrLabels: string;
|
||||||
|
onlyLabels: string;
|
||||||
|
onlyIssueLabels: string;
|
||||||
|
onlyPrLabels: string;
|
||||||
|
anyOfLabels: string;
|
||||||
|
operationsPerRun: number;
|
||||||
|
removeStaleWhenUpdated: boolean;
|
||||||
|
debugOnly: boolean;
|
||||||
|
ascending: boolean;
|
||||||
|
skipStaleIssueMessage: boolean;
|
||||||
|
skipStalePrMessage: boolean;
|
||||||
|
deleteBranch: boolean;
|
||||||
|
startDate: IsoOrRfcDateString | undefined; // Should be ISO 8601 or RFC 2822
|
||||||
|
exemptMilestones: string;
|
||||||
|
exemptIssueMilestones: string;
|
||||||
|
exemptPrMilestones: string;
|
||||||
|
exemptAllMilestones: boolean;
|
||||||
|
exemptAllIssueMilestones: boolean | undefined;
|
||||||
|
exemptAllPrMilestones: boolean | undefined;
|
||||||
|
exemptAssignees: string;
|
||||||
|
exemptIssueAssignees: string;
|
||||||
|
exemptPrAssignees: string;
|
||||||
|
exemptAllAssignees: boolean;
|
||||||
|
exemptAllIssueAssignees: boolean | undefined;
|
||||||
|
exemptAllPrAssignees: boolean | undefined;
|
||||||
|
enableStatistics: boolean;
|
||||||
|
}
|
||||||
3
src/interfaces/label.ts
Normal file
3
src/interfaces/label.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface ILabel {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
3
src/interfaces/milestone.ts
Normal file
3
src/interfaces/milestone.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface IMilestone {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
6
src/interfaces/pull-request.ts
Normal file
6
src/interfaces/pull-request.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface IPullRequest {
|
||||||
|
number: number;
|
||||||
|
head: {
|
||||||
|
ref: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
4
src/interfaces/user.ts
Normal file
4
src/interfaces/user.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface IUser {
|
||||||
|
type: string | 'User';
|
||||||
|
login: string;
|
||||||
|
}
|
||||||
70
src/main.ts
70
src/main.ts
@@ -1,11 +1,13 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import {IssueProcessor, IssueProcessorOptions} from './IssueProcessor';
|
import {isValidDate} from './functions/dates/is-valid-date';
|
||||||
|
import {IssuesProcessor} from './classes/issues-processor';
|
||||||
|
import {IIssuesProcessorOptions} from './interfaces/issues-processor-options';
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function _run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const args = getAndValidateArgs();
|
const args = _getAndValidateArgs();
|
||||||
|
|
||||||
const processor: IssueProcessor = new IssueProcessor(args);
|
const processor: IssuesProcessor = new IssuesProcessor(args);
|
||||||
await processor.processIssues();
|
await processor.processIssues();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.error(error);
|
core.error(error);
|
||||||
@@ -13,9 +15,9 @@ async function run(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAndValidateArgs(): IssueProcessorOptions {
|
function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||||
const args = {
|
const args: IIssuesProcessorOptions = {
|
||||||
repoToken: core.getInput('repo-token', {required: true}),
|
repoToken: core.getInput('repo-token'),
|
||||||
staleIssueMessage: core.getInput('stale-issue-message'),
|
staleIssueMessage: core.getInput('stale-issue-message'),
|
||||||
stalePrMessage: core.getInput('stale-pr-message'),
|
stalePrMessage: core.getInput('stale-pr-message'),
|
||||||
closeIssueMessage: core.getInput('close-issue-message'),
|
closeIssueMessage: core.getInput('close-issue-message'),
|
||||||
@@ -23,9 +25,13 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||||||
daysBeforeStale: parseInt(
|
daysBeforeStale: parseInt(
|
||||||
core.getInput('days-before-stale', {required: true})
|
core.getInput('days-before-stale', {required: true})
|
||||||
),
|
),
|
||||||
|
daysBeforeIssueStale: parseInt(core.getInput('days-before-issue-stale')),
|
||||||
|
daysBeforePrStale: parseInt(core.getInput('days-before-pr-stale')),
|
||||||
daysBeforeClose: parseInt(
|
daysBeforeClose: parseInt(
|
||||||
core.getInput('days-before-close', {required: true})
|
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}),
|
staleIssueLabel: core.getInput('stale-issue-label', {required: true}),
|
||||||
closeIssueLabel: core.getInput('close-issue-label'),
|
closeIssueLabel: core.getInput('close-issue-label'),
|
||||||
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
||||||
@@ -33,6 +39,9 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||||||
closePrLabel: core.getInput('close-pr-label'),
|
closePrLabel: core.getInput('close-pr-label'),
|
||||||
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
||||||
onlyLabels: core.getInput('only-labels'),
|
onlyLabels: core.getInput('only-labels'),
|
||||||
|
onlyIssueLabels: core.getInput('only-issue-labels'),
|
||||||
|
onlyPrLabels: core.getInput('only-pr-labels'),
|
||||||
|
anyOfLabels: core.getInput('any-of-labels'),
|
||||||
operationsPerRun: parseInt(
|
operationsPerRun: parseInt(
|
||||||
core.getInput('operations-per-run', {required: true})
|
core.getInput('operations-per-run', {required: true})
|
||||||
),
|
),
|
||||||
@@ -42,7 +51,25 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||||||
debugOnly: core.getInput('debug-only') === 'true',
|
debugOnly: core.getInput('debug-only') === 'true',
|
||||||
ascending: core.getInput('ascending') === 'true',
|
ascending: core.getInput('ascending') === 'true',
|
||||||
skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
|
skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
|
||||||
skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true'
|
skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true',
|
||||||
|
deleteBranch: core.getInput('delete-branch') === 'true',
|
||||||
|
startDate:
|
||||||
|
core.getInput('start-date') !== ''
|
||||||
|
? core.getInput('start-date')
|
||||||
|
: undefined,
|
||||||
|
exemptMilestones: core.getInput('exempt-milestones'),
|
||||||
|
exemptIssueMilestones: core.getInput('exempt-issue-milestones'),
|
||||||
|
exemptPrMilestones: core.getInput('exempt-pr-milestones'),
|
||||||
|
exemptAllMilestones: core.getInput('exempt-all-milestones') === 'true',
|
||||||
|
exemptAllIssueMilestones: _toOptionalBoolean('exempt-all-issue-milestones'),
|
||||||
|
exemptAllPrMilestones: _toOptionalBoolean('exempt-all-pr-milestones'),
|
||||||
|
exemptAssignees: core.getInput('exempt-assignees'),
|
||||||
|
exemptIssueAssignees: core.getInput('exempt-issue-assignees'),
|
||||||
|
exemptPrAssignees: core.getInput('exempt-pr-assignees'),
|
||||||
|
exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true',
|
||||||
|
exemptAllIssueAssignees: _toOptionalBoolean('exempt-all-issue-assignees'),
|
||||||
|
exemptAllPrAssignees: _toOptionalBoolean('exempt-all-pr-assignees'),
|
||||||
|
enableStatistics: core.getInput('enable-statistics') === 'true'
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const numberInput of [
|
for (const numberInput of [
|
||||||
@@ -55,7 +82,32 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const optionalDateInput of ['start-date']) {
|
||||||
|
// Ignore empty dates because it is considered as the right type for a default value (so a valid one)
|
||||||
|
if (core.getInput(optionalDateInput) !== '') {
|
||||||
|
if (!isValidDate(new Date(core.getInput(optionalDateInput)))) {
|
||||||
|
throw new Error(
|
||||||
|
`input ${optionalDateInput} did not parse to a valid date`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
run();
|
function _toOptionalBoolean(
|
||||||
|
argumentName: Readonly<string>
|
||||||
|
): boolean | undefined {
|
||||||
|
const argument: string = core.getInput(argumentName);
|
||||||
|
|
||||||
|
if (argument === 'true') {
|
||||||
|
return true;
|
||||||
|
} else if (argument === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _run();
|
||||||
|
|||||||
1
src/types/clean-label.ts
Normal file
1
src/types/clean-label.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type CleanLabel = string;
|
||||||
1
src/types/humanized-date.ts
Normal file
1
src/types/humanized-date.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type HumanizedDate = string;
|
||||||
1
src/types/iso-date-string.ts
Normal file
1
src/types/iso-date-string.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type IsoDateString = string;
|
||||||
1
src/types/iso-or-rfc-date-string.ts
Normal file
1
src/types/iso-or-rfc-date-string.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type IsoOrRfcDateString = string;
|
||||||
5
tsconfig.app.json
Normal file
5
tsconfig.app.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "**/*.spec.ts"],
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
"outDir": "./lib" /* Redirect output structure to the directory. */,
|
||||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
|
||||||
//"sourceMap": true
|
//"sourceMap": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "**/*.test.ts"]
|
"include": ["src", "__tests__"]
|
||||||
}
|
}
|
||||||
|
|||||||
5
tsconfig.spec.json
Normal file
5
tsconfig.spec.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["src", "__tests__"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user