diff --git a/README.md b/README.md
index 1e01ffd..2d0bf3a 100644
--- a/README.md
+++ b/README.md
@@ -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.
-
+
## 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
-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.
@@ -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.
@@ -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.
```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.
```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.
```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.
```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.
```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.
```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.
```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.
```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
diff --git a/action.yml b/action.yml
index 36aff90..881d669 100644
--- a/action.yml
+++ b/action.yml
@@ -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'
diff --git a/src/data.js b/src/data.js
new file mode 100644
index 0000000..740d1d9
--- /dev/null
+++ b/src/data.js
@@ -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
+};
diff --git a/src/index.js b/src/index.js
index bda1cb8..acf48f6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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;
+ } else {
+ try {
+ await this.client.rest.issues.createComment({
+ ...thread,
+ 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});
+ 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}));
+ }
if (addLabels) {
- const currentLabels = issueData.labels.map(label => label.name);
+ const currentLabelNames = currentLabels.map(label => label.name);
const newLabels = addLabels.filter(
- label => !currentLabels.includes(label)
+ label => !currentLabelNames.includes(label)
);
if (newLabels.length) {
- core.debug(`Labeling (${type}: ${issue.issue_number})`);
- await this.client.rest.issues.addLabels({
- ...issue,
- labels: newLabels
- });
+ 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 currentLabels = issueData.labels.map(label => label.name);
const matchingLabels = currentLabels.filter(label =>
- removeLabels.includes(label)
+ removeLabels.includes(label.name)
);
+
if (matchingLabels.length) {
- core.debug(`Unlabeling (${type}: ${issue.issue_number})`);
- for (const label of matchingLabels) {
- await this.client.rest.issues.removeLabel({
- ...issue,
- name: label
+ 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
+ });
+ }
}
}
}
}
- core.debug(`Locking (${type}: ${issue.issue_number})`);
+ core.debug(`Locking (${threadType}: ${threadNumber})`);
- const params = {...issue};
+ if (threadType === 'discussion') {
+ await this.client.graphql(lockLockableQuery, {
+ lockableId: discussionId
+ });
+ } else {
+ const params = {...thread};
- if (lockReason) {
- params.lock_reason = lockReason;
- }
+ if (lockReason) {
+ params.lock_reason = lockReason;
+ }
- await this.client.rest.issues.lock(params);
+ await this.client.rest.issues.lock(params);
+ }
- threads.push(issue);
+ 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();
diff --git a/src/schema.js b/src/schema.js
index 95d9f68..5e119c7 100644
--- a/src/schema.js
+++ b/src/schema.js
@@ -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)
diff --git a/src/utils.js b/src/utils.js
index cf21b95..9d40057 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -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};