feat: lock discussions

BREAKING CHANGE: Discussions are also processed by default,
set the `process-only` input parameter to preserve the old behavior

```yaml
    steps:
      - uses: dessant/lock-threads@v5
        with:
          process-only: 'issues, prs'
```

Closes #25.
This commit is contained in:
dessant
2023-11-14 07:03:53 +02:00
parent 53f3f0c0b9
commit 0a0976f3de
6 changed files with 527 additions and 149 deletions

166
README.md
View File

@@ -1,9 +1,9 @@
# Lock Threads
Lock Threads is a GitHub Action that locks closed issues
and pull requests after a period of inactivity.
Lock Threads is a GitHub Action that locks closed issues,
pull requests and discussions after a period of inactivity.
<img width="800" src="https://raw.githubusercontent.com/dessant/lock-threads/master/assets/screenshot.png">
<img width="800" src="https://raw.githubusercontent.com/dessant/lock-threads/main/assets/screenshot.png">
## Supporting the Project
@@ -16,13 +16,13 @@ please consider contributing with
## Usage
Create the `lock.yml` workflow file in the `.github/workflows` directory,
use one of the [example workflows](#examples) to get started.
Create the `lock-threads.yml` workflow file in the `.github/workflows`
directory, use one of the [example workflows](#examples) to get started.
### Inputs
<!-- prettier-ignore -->
The action can be configured using [input parameters](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith).
The action can be configured using [input parameters](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepswith).
- **`github-token`**
- GitHub access token, value must be `${{ github.token }}` or an encrypted
@@ -146,9 +146,65 @@ The action can be configured using [input parameters](https://docs.github.com/en
- Reason for locking a pull request, value must be one
of `resolved`, `off-topic`, `too heated`, `spam` or `''`
- Optional, defaults to `resolved`
- **`discussion-inactive-days`**
- Number of days of inactivity before a closed discussion is locked
- Optional, defaults to `365`
- **`exclude-discussion-created-before`**
- Do not lock discussions created before a given date,
value must follow ISO 8601, ignored
when `exclude-discussion-created-between` is set
- Optional, defaults to `''`
- **`exclude-discussion-created-after`**
- Do not lock discussions created after a given date,
value must follow ISO 8601, ignored
when `exclude-discussion-created-between` is set
- Optional, defaults to `''`
- **`exclude-discussion-created-between`**
- Do not lock discussions created in a given time interval,
value must follow ISO 8601
- Optional, defaults to `''`
- **`exclude-discussion-closed-before`**
- Do not lock discussions closed before a given date,
value must follow ISO 8601, ignored
when `exclude-discussion-closed-between` is set
- Optional, defaults to `''`
- **`exclude-discussion-closed-after`**
- Do not lock discussions closed after a given date,
value must follow ISO 8601, ignored
when `exclude-discussion-closed-between` is set
- Optional, defaults to `''`
- **`exclude-discussion-closed-between`**
- Do not lock discussions closed in a given time interval,
value must follow ISO 8601
- Optional, defaults to `''`
- **`include-any-discussion-labels`**
- Only lock discussions with any of these labels, value must be
a comma separated list of labels or `''`, ignored
when `include-all-discussion-labels` is set
- Optional, defaults to `''`
- **`include-all-discussion-labels`**
- Only lock discussions with all these labels, value must be
a comma separated list of labels or `''`
- Optional, defaults to `''`
- **`exclude-any-discussion-labels`**
- Do not lock discussions with any of these labels, value must be
a comma separated list of labels or `''`
- Optional, defaults to `''`
- **`add-discussion-labels`**
- Labels to add before locking a discussion, value must be
a comma separated list of labels or `''`
- Optional, defaults to `''`
- **`remove-discussion-labels`**
- Labels to remove before locking a discussion, value must be
a comma separated list of labels or `''`
- Optional, defaults to `''`
- **`discussion-comment`**
- Comment to post before locking a discussion
- Optional, defaults to `''`
- **`process-only`**
- Limit locking to only issues or pull requests, value must be
one of `issues`, `prs` or `''`
- Only lock issues, pull requests or discussions,
value must be a comma separated list, list items must be
one of `issues`, `prs` or `discussions`
- Optional, defaults to `''`
- **`log-output`**
- Log output parameters, value must be either `true` or `false`
@@ -165,11 +221,15 @@ The action can be configured using [input parameters](https://docs.github.com/en
- Pull requests that have been locked, value is a JSON string in the form
of `[{"owner": "actions", "repo": "toolkit", "issue_number": 1}]`
- Defaults to `''`
- **`discussions`**
- Discussions that have been locked, value is a JSON string in the form
of `[{"owner": "actions", "repo": "toolkit", "discussion_number": 1}]`
- Defaults to `''`
## Examples
The following workflow will search once an hour for closed issues
and pull requests that have not had any activity
The following workflow will search once an hour for closed issues,
pull requests and discussions that have not had any activity
in the past year and can be locked.
<!-- prettier-ignore -->
@@ -184,19 +244,20 @@ on:
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: lock
group: lock-threads
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
```
Edit the workflow after the initial backlog of issues and pull requests
has been processed to reduce the frequency of scheduled runs.
Edit the workflow after the initial backlog of issues, pull requests
and discussions has been processed to reduce the frequency of scheduled runs.
Running the workflow only once a day helps reduce resource usage.
<!-- prettier-ignore -->
@@ -223,15 +284,16 @@ on:
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: lock
group: lock-threads
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
github-token: ${{ github.token }}
issue-inactive-days: '365'
@@ -262,11 +324,24 @@ jobs:
remove-pr-labels: ''
pr-comment: ''
pr-lock-reason: 'resolved'
discussion-inactive-days: '365'
exclude-discussion-created-before: ''
exclude-discussion-created-after: ''
exclude-discussion-created-between: ''
exclude-discussion-closed-before: ''
exclude-discussion-closed-after: ''
exclude-discussion-closed-between: ''
include-any-discussion-labels: ''
include-all-discussion-labels: ''
exclude-any-discussion-labels: ''
add-discussion-labels: ''
remove-discussion-labels: ''
discussion-comment: ''
process-only: ''
log-output: false
```
### Filtering issues and pull requests
### Filtering issues, pull requests and discussions
This step will lock only issues, and exclude issues created before 2018,
or those with the `upstream` or `help-wanted` labels applied.
@@ -274,7 +349,7 @@ or those with the `upstream` or `help-wanted` labels applied.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
exclude-issue-created-before: '2018-01-01T00:00:00Z'
exclude-any-issue-labels: 'upstream, help-wanted'
@@ -287,7 +362,7 @@ with the `wip` label applied.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
exclude-any-pr-labels: 'wip'
process-only: 'prs'
@@ -299,7 +374,7 @@ or those created in 2018 and 2019.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
exclude-issue-created-between: '2018-01-01T00:00:00Z/2019-12-31T23:59:59.999Z'
exclude-issue-closed-before: '2018-01-01T00:00:00Z'
@@ -313,22 +388,24 @@ labels applied.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
include-any-issue-labels: 'incomplete, invalid'
include-all-pr-labels: 'qa: done, published'
process-only: 'issues, prs'
```
This step will lock issues that have not had any activity in the past 180 days.
This step will lock discussions that have not had any activity
in the past 180 days.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
issue-inactive-days: '180'
process-only: 'issues'
discussion-inactive-days: '180'
process-only: 'discussions'
```
@@ -340,7 +417,7 @@ and apply the `outdated` label to issues.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
add-issue-labels: 'outdated'
issue-comment: >
@@ -351,6 +428,7 @@ and apply the `outdated` label to issues.
This pull request has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.
process-only: 'issues, prs'
```
This step will apply the `qa: done` and `archived` labels,
@@ -360,10 +438,11 @@ before locking issues.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
add-issue-labels: 'qa: done, archived'
remove-issue-labels: 'qa: primary, needs: user feedback'
process-only: 'issues'
```
### Using a personal access token
@@ -372,39 +451,38 @@ The action uses an installation access token by default to interact with GitHub.
You may also authenticate with a personal access token to perform actions
as a GitHub user instead of the `github-actions` app.
Create a [personal access token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token)
Create a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic)
with the `repo` or `public_repo` scopes enabled, and add the token as an
[encrypted secret](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
[encrypted secret](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository)
for the repository or organization, then provide the action with the secret
using the `github-token` input parameter.
<!-- prettier-ignore -->
```yaml
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
```
## How are issues and pull requests determined to be inactive?
## How are issues, pull requests and discussions determined to be inactive?
The action uses GitHub's [updated](https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests#search-by-when-an-issue-or-pull-request-was-created-or-last-updated)
search qualifier to determine inactivity. Any change to an issue or pull request
is considered an update, including comments, changing labels,
applying or removing milestones, or pushing commits.
The action uses GitHub's [updated](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests#search-by-when-an-issue-or-pull-request-was-created-or-last-updated)
search qualifier to determine inactivity. Any change to an issue, pull request
or discussion is considered an update, including new comments,
or changing labels.
An easy way to check and see which issues or pull requests will initially
be locked is to add the `updated` search qualifier to either the issue
or pull request search field for your repository:
An easy way to see which threads will initially be locked is to add
the `updated` search qualifier to the issue, pull request or discussion
search field for your repository, adjust the date based on the value
of the `*-inactive-days` input parameter:
`is:closed is:unlocked updated:<2018-12-20`.
Adjust the date to be 365 days ago (or whatever you set for `*-inactive-days`)
to see which issues or pull requests will be locked.
## Why are only some issues and pull requests processed?
## Why are only some issues, pull requests and discussions processed?
To avoid triggering abuse prevention mechanisms on GitHub, only 50 issues
and pull requests will be handled at once. If your repository has more
than that, it will just take a few hours or days to process them all.
To avoid triggering abuse prevention mechanisms on GitHub, only 50 threads
will be handled at a time. If your repository has more than that,
it will take a few hours or days to process them all.
## License

View File

@@ -1,5 +1,5 @@
name: 'Lock Threads'
description: 'Lock closed issues and pull requests after a period of inactivity'
description: 'Lock closed issues, pull requests and discussions after a period of inactivity'
author: 'Armin Sebastian'
inputs:
github-token:
@@ -89,8 +89,47 @@ inputs:
pr-lock-reason:
description: 'Reason for locking a pull request, value must be one of `resolved`, `off-topic`, `too heated` or `spam`'
default: 'resolved'
discussion-inactive-days:
description: 'Number of days of inactivity before a closed discussion is locked'
default: '365'
exclude-discussion-created-before:
description: 'Do not lock discussions created before a given date, value must follow ISO 8601'
default: ''
exclude-discussion-created-after:
description: 'Do not lock discussions created after a given date, value must follow ISO 8601'
default: ''
exclude-discussion-created-between:
description: 'Do not lock discussions created in a given time interval, value must follow ISO 8601'
default: ''
exclude-discussion-closed-before:
description: 'Do not lock discussions closed before a given date, value must follow ISO 8601'
default: ''
exclude-discussion-closed-after:
description: 'Do not lock discussions closed after a given date, value must follow ISO 8601'
default: ''
exclude-discussion-closed-between:
description: 'Do not lock discussions closed in a given time interval, value must follow ISO 8601'
default: ''
include-any-discussion-labels:
description: 'Only lock issues with any of these labels, value must be a comma separated list of labels'
default: ''
include-all-discussion-labels:
description: 'Only lock discussions with all these labels, value must be a comma separated list of labels'
default: ''
exclude-any-discussion-labels:
description: 'Do not lock discussions with any of these labels, value must be a comma separated list of labels'
default: ''
add-discussion-labels:
description: 'Labels to add before locking a discussion, value must be a comma separated list of labels'
default: ''
remove-discussion-labels:
description: 'Labels to remove before locking a discussion, value must be a comma separated list of labels'
default: ''
discussion-comment:
description: 'Comment to post before locking a discussion'
default: ''
process-only:
description: 'Limit locking to only issues or pull requests, value must be one of `issues` or `prs`'
description: 'Only lock issues, pull requests or discussions, value must be a comma separated list, list items must be one of `issues`, `prs` or `discussions`'
default: ''
log-output:
description: 'Log output parameters, value must be either `true` or `false`'
@@ -100,6 +139,8 @@ outputs:
description: 'Issues that have been locked, value is a JSON string'
prs:
description: 'Pull requests that have been locked, value is a JSON string'
discussions:
description: 'Discussions that have been locked, value is a JSON string'
runs:
using: 'node20'
main: 'dist/index.js'

113
src/data.js Normal file
View File

@@ -0,0 +1,113 @@
const addDiscussionCommentQuery = `
mutation ($discussionId: ID!, $body: String!) {
addDiscussionComment(input: {discussionId: $discussionId, body: $body}) {
comment {
id
}
}
}
`;
const getLabelQuery = `
query ($owner: String!, $repo: String!, $label: String!) {
repository(owner: $owner, name: $repo) {
label(name: $label) {
id
name
}
}
}
`;
const createLabelQuery = `
mutation ($repositoryId: ID!, $name: String!, $color: String!) {
createLabel(input: {repositoryId: $repositoryId, name: $name, , color: $color}) {
label {
id
name
}
}
}
`;
const getDiscussionLabelsQuery = `
query ($owner: String!, $repo: String!, $discussion: Int!) {
repository(owner: $owner, name: $repo) {
discussion(number: $discussion) {
number
labels(first: 100) {
nodes {
id
name
}
}
}
}
}
`;
const addLabelsToLabelableQuery = `
mutation ($labelableId: ID!, $labelIds: [ID!]!) {
addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) {
labelable {
labels(first: 0) {
edges {
node {
id
}
}
}
}
}
}
`;
const removeLabelsFromLabelableQuery = `
mutation ($labelableId: ID!, $labelIds: [ID!]!) {
removeLabelsFromLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) {
labelable {
labels(first: 0) {
edges {
node {
id
}
}
}
}
}
}
`;
const lockLockableQuery = `
mutation ($lockableId: ID!) {
lockLockable(input: {lockableId: $lockableId}) {
lockedRecord {
locked
}
}
}
`;
const searchDiscussionsQuery = `
query ($q: String!) {
search(type: DISCUSSION, first: 50, query: $q) {
nodes {
... on Discussion {
id
number
}
}
}
}
`;
export {
searchDiscussionsQuery,
addDiscussionCommentQuery,
getLabelQuery,
createLabelQuery,
getDiscussionLabelsQuery,
addLabelsToLabelableQuery,
removeLabelsFromLabelableQuery,
lockLockableQuery
};

View File

@@ -1,8 +1,17 @@
import core from '@actions/core';
import github from '@actions/github';
import {schema} from './schema.js';
import {getClient} from './utils.js';
import {getConfig, getClient} from './utils.js';
import {
searchDiscussionsQuery,
addDiscussionCommentQuery,
getLabelQuery,
createLabelQuery,
getDiscussionLabelsQuery,
addLabelsToLabelableQuery,
removeLabelsFromLabelableQuery,
lockLockableQuery
} from './data.js';
async function run() {
try {
@@ -23,10 +32,10 @@ class App {
}
async lockThreads() {
const type = this.config['process-only'];
const processOnly = this.config['process-only'];
const logOutput = this.config['log-output'];
const threadTypes = type ? [type] : ['issue', 'pr'];
const threadTypes = processOnly || ['issue', 'pr', 'discussion'];
for (const item of threadTypes) {
const threads = await this.lock(item);
@@ -44,93 +53,173 @@ class App {
}
}
async lock(type) {
const repo = github.context.repo;
const addLabels = this.config[`add-${type}-labels`];
const removeLabels = this.config[`remove-${type}-labels`];
const comment = this.config[`${type}-comment`];
const lockReason = this.config[`${type}-lock-reason`];
async lock(threadType) {
const {owner, repo} = github.context.repo;
const addLabels = this.config[`add-${threadType}-labels`];
const removeLabels = this.config[`remove-${threadType}-labels`];
const comment = this.config[`${threadType}-comment`];
const lockReason = this.config[`${threadType}-lock-reason`];
const threads = [];
const results = await this.search(type);
const results = await this.search(threadType);
for (const result of results) {
const issue = {...repo, issue_number: result.number};
const thread =
threadType === 'discussion'
? {owner, repo, discussion_number: result.number}
: {owner, repo, issue_number: result.number};
const threadNumber = thread.discussion_number || thread.issue_number;
const discussionId = result.id;
if (comment) {
core.debug(`Commenting (${type}: ${issue.issue_number})`);
try {
await this.client.rest.issues.createComment({
...issue,
core.debug(`Commenting (${threadType}: ${threadNumber})`);
if (threadType === 'discussion') {
await this.client.graphql(addDiscussionCommentQuery, {
discussionId,
body: comment
});
} catch (err) {
if (!/cannot be modified.*discussion/i.test(err.message)) {
throw err;
}
}
}
if (addLabels || removeLabels) {
const {data: issueData} = await this.client.rest.issues.get({...issue});
if (addLabels) {
const currentLabels = issueData.labels.map(label => label.name);
const newLabels = addLabels.filter(
label => !currentLabels.includes(label)
);
if (newLabels.length) {
core.debug(`Labeling (${type}: ${issue.issue_number})`);
await this.client.rest.issues.addLabels({
...issue,
labels: newLabels
} else {
try {
await this.client.rest.issues.createComment({
...thread,
body: comment
});
}
}
if (removeLabels) {
const currentLabels = issueData.labels.map(label => label.name);
const matchingLabels = currentLabels.filter(label =>
removeLabels.includes(label)
);
if (matchingLabels.length) {
core.debug(`Unlabeling (${type}: ${issue.issue_number})`);
for (const label of matchingLabels) {
await this.client.rest.issues.removeLabel({
...issue,
name: label
});
} catch (err) {
if (!/cannot be modified.*discussion/i.test(err.message)) {
throw err;
}
}
}
}
core.debug(`Locking (${type}: ${issue.issue_number})`);
if (addLabels || removeLabels) {
let currentLabels;
if (threadType === 'discussion') {
({
repository: {
discussion: {
labels: {nodes: currentLabels}
}
}
} = await this.client.graphql(getDiscussionLabelsQuery, {
owner,
repo,
discussion: thread.discussion_number
}));
} else {
({
data: {labels: currentLabels}
} = await this.client.rest.issues.get({...thread}));
}
const params = {...issue};
if (addLabels) {
const currentLabelNames = currentLabels.map(label => label.name);
const newLabels = addLabels.filter(
label => !currentLabelNames.includes(label)
);
if (lockReason) {
params.lock_reason = lockReason;
if (newLabels.length) {
core.debug(`Labeling (${threadType}: ${threadNumber})`);
if (threadType === 'discussion') {
const labels = [];
for (const labelName of newLabels) {
let {
repository: {label}
} = await this.client.graphql(getLabelQuery, {
owner,
repo,
label: labelName
});
if (!label) {
({
createLabel: {label}
} = await this.client.graphql(createLabelQuery, {
repositoryId: github.context.payload.repository.node_id,
name: labelName,
color: 'ffffff',
headers: {
Accept: 'application/vnd.github.bane-preview+json'
}
}));
}
labels.push(label);
}
await this.client.graphql(addLabelsToLabelableQuery, {
labelableId: discussionId,
labelIds: labels.map(label => label.id)
});
} else {
await this.client.rest.issues.addLabels({
...thread,
labels: newLabels
});
}
}
}
if (removeLabels) {
const matchingLabels = currentLabels.filter(label =>
removeLabels.includes(label.name)
);
if (matchingLabels.length) {
core.debug(`Unlabeling (${threadType}: ${threadNumber})`);
if (threadType === 'discussion') {
await this.client.graphql(removeLabelsFromLabelableQuery, {
labelableId: discussionId,
labelIds: matchingLabels.map(label => label.id)
});
} else {
for (const label of matchingLabels) {
await this.client.rest.issues.removeLabel({
...thread,
name: label.name
});
}
}
}
}
}
await this.client.rest.issues.lock(params);
core.debug(`Locking (${threadType}: ${threadNumber})`);
threads.push(issue);
if (threadType === 'discussion') {
await this.client.graphql(lockLockableQuery, {
lockableId: discussionId
});
} else {
const params = {...thread};
if (lockReason) {
params.lock_reason = lockReason;
}
await this.client.rest.issues.lock(params);
}
threads.push(thread);
}
return threads;
}
async search(type) {
async search(threadType) {
const {owner, repo} = github.context.repo;
const updatedTime = this.getUpdatedTimestamp(
this.config[`${type}-inactive-days`]
this.config[`${threadType}-inactive-days`]
);
let query = `repo:${owner}/${repo} updated:<${updatedTime} is:closed is:unlocked`;
const includeAnyLabels = this.config[`include-any-${type}-labels`];
const includeAllLabels = this.config[`include-all-${type}-labels`];
const includeAnyLabels = this.config[`include-any-${threadType}-labels`];
const includeAllLabels = this.config[`include-all-${threadType}-labels`];
if (includeAllLabels) {
query += ` ${includeAllLabels
@@ -140,13 +229,13 @@ class App {
query += ` label:${includeAnyLabels.join(',')}`;
}
const excludeAnyLabels = this.config[`exclude-any-${type}-labels`];
const excludeAnyLabels = this.config[`exclude-any-${threadType}-labels`];
if (excludeAnyLabels) {
query += ` -label:${excludeAnyLabels.join(',')}`;
}
const excludeCreatedQuery = this.getFilterByDateQuery({
type,
threadType,
qualifier: 'created'
});
if (excludeCreatedQuery) {
@@ -154,37 +243,48 @@ class App {
}
const excludeClosedQuery = this.getFilterByDateQuery({
type,
threadType,
qualifier: 'closed'
});
if (excludeClosedQuery) {
query += ` ${excludeClosedQuery}`;
}
if (type === 'issue') {
if (threadType === 'issue') {
query += ' is:issue';
} else {
} else if (threadType === 'pr') {
query += ' is:pr';
}
core.debug(`Searching (${type}s)`);
const results = (
await this.client.rest.search.issuesAndPullRequests({
core.debug(`Searching (${threadType}s)`);
let results;
if (threadType === 'discussion') {
({
search: {nodes: results}
} = await this.client.graphql(searchDiscussionsQuery, {q: query}));
} else {
({
data: {items: results}
} = await this.client.rest.search.issuesAndPullRequests({
q: query,
sort: 'updated',
order: 'desc',
per_page: 50
})
).data.items;
}));
// results may include locked issues
return results.filter(issue => !issue.locked);
// results may include locked threads
results = results.filter(item => !item.locked);
}
return results;
}
getFilterByDateQuery({type, qualifier = 'created'} = {}) {
const beforeDate = this.config[`exclude-${type}-${qualifier}-before`];
const afterDate = this.config[`exclude-${type}-${qualifier}-after`];
const betweenDates = this.config[`exclude-${type}-${qualifier}-between`];
getFilterByDateQuery({threadType, qualifier = 'created'} = {}) {
const beforeDate = this.config[`exclude-${threadType}-${qualifier}-before`];
const afterDate = this.config[`exclude-${threadType}-${qualifier}-after`];
const betweenDates =
this.config[`exclude-${threadType}-${qualifier}-between`];
if (betweenDates) {
return `-${qualifier}:${betweenDates
@@ -212,17 +312,4 @@ class App {
}
}
function getConfig() {
const input = Object.fromEntries(
Object.keys(schema.describe().keys).map(item => [item, core.getInput(item)])
);
const {error, value} = schema.validate(input, {abortEarly: false});
if (error) {
throw error;
}
return value;
}
run();

View File

@@ -58,13 +58,23 @@ const extendedJoi = Joi.extend(joi => {
.extend(joi => {
return {
type: 'processOnly',
base: joi.string(),
base: joi.array(),
coerce: {
from: 'string',
method(value, helpers) {
method(value) {
value = value.trim();
if (['issues', 'prs'].includes(value)) {
value = value.slice(0, -1);
if (value) {
value = value
.split(',')
.map(item => {
item = item.trim();
if (['issues', 'prs', 'discussions'].includes(item)) {
item = item.slice(0, -1);
}
return item;
})
.filter(Boolean);
}
return {value};
@@ -74,10 +84,7 @@ const extendedJoi = Joi.extend(joi => {
});
const joiDate = Joi.alternatives().try(
Joi.date()
// .iso()
.min('1970-01-01T00:00:00Z')
.max('2970-12-31T23:59:59Z'),
Joi.date().iso().min('1970-01-01T00:00:00Z').max('2970-12-31T23:59:59Z'),
Joi.string().trim().valid('')
);
@@ -169,9 +176,46 @@ const schema = Joi.object({
.valid('resolved', 'off-topic', 'too heated', 'spam', '')
.default('resolved'),
'process-only': extendedJoi
.processOnly()
.valid('issue', 'pr', '')
'discussion-inactive-days': Joi.number()
.min(0)
.max(3650)
.precision(9)
.default(365),
'exclude-discussion-created-before': joiDate.default(''),
'exclude-discussion-created-after': joiDate.default(''),
'exclude-discussion-created-between': joiTimeInterval.default(''),
'exclude-discussion-closed-before': joiDate.default(''),
'exclude-discussion-closed-after': joiDate.default(''),
'exclude-discussion-closed-between': joiTimeInterval.default(''),
'include-any-discussion-labels': joiLabels.default(''),
'include-all-discussion-labels': joiLabels.default(''),
'exclude-any-discussion-labels': joiLabels.default(''),
'add-discussion-labels': joiLabels.default(''),
'remove-discussion-labels': joiLabels.default(''),
'discussion-comment': Joi.string().trim().max(10000).allow('').default(''),
'process-only': Joi.alternatives()
.try(
extendedJoi
.processOnly()
.items(Joi.string().valid('issue', 'pr', 'discussion'))
.min(1)
.max(3)
.unique(),
Joi.string().trim().valid('')
)
.default(''),
'log-output': Joi.boolean().default(false)

View File

@@ -3,6 +3,21 @@ import github from '@actions/github';
import {retry} from '@octokit/plugin-retry';
import {throttling} from '@octokit/plugin-throttling';
import {schema} from './schema.js';
function getConfig() {
const input = Object.fromEntries(
Object.keys(schema.describe().keys).map(item => [item, core.getInput(item)])
);
const {error, value} = schema.validate(input, {abortEarly: false});
if (error) {
throw error;
}
return value;
}
function getClient(token) {
const requestRetries = 3;
@@ -34,4 +49,4 @@ function getClient(token) {
return github.getOctokit(token, options, retry, throttling);
}
export {getClient};
export {getConfig, getClient};