diff --git a/.github/workflows/monitor-inactive-issues.yml b/.github/workflows/monitor-inactive-issues.yml
new file mode 100644
index 00000000000..d6964b67dd6
--- /dev/null
+++ b/.github/workflows/monitor-inactive-issues.yml
@@ -0,0 +1,48 @@
+name: Monitor Inactive Open Issues
+#Runs every Monday and Thursday at 9:00 A.M
+on:
+ schedule:
+ - cron: "0 9 * * 1,4"
+env:
+ inactiveIntervalDays: ${{ vars.MONITORING_INACTIVE_INTERVAL_DAYS }}
+jobs:
+ retrieve-inactive-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ outputs:
+ issues: ${{ steps.filter-inactive-issues.outputs.result }}
+ steps:
+ - name: Check environment
+ run: |
+ if [ -z $inactiveIntervalDays ]; then
+ echo "::error::'MONITORING_INACTIVE_INTERVAL_DAYS' environment variable is not set"
+ exit 1
+ fi
+ - uses: actions/checkout@v3
+ if: success()
+ - name: Filter inactive issues
+ id: filter-inactive-issues
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const script = require('./.github/workflows/scripts/filterInactiveIssues.js');
+ return await script({github, context, core});
+ notify-inactive-issues:
+ runs-on: ubuntu-latest
+ needs: retrieve-inactive-issues
+ if: ${{ needs.retrieve-inactive-issues.outputs.issues }}
+ strategy:
+ matrix:
+ issues: ${{ fromJSON(needs.retrieve-inactive-issues.outputs.issues) }}
+ steps:
+ - name: Notify MS Teams channel
+ id: notify-ms-teams
+ uses: simbo/msteams-message-card-action@latest
+ with:
+ webhook: ${{ secrets.COMMUNITY_EVENTS_WEBHOOK_URL }}
+ title: Inactive Issue Detected
+ message: |
+ It's been ${{ env.inactiveIntervalDays }} days since issue number ${{ matrix.issues.number }}
has received an update. ${{ matrix.issues.assignee }}, please provide an update soon.
+ buttons: |
+ View Issue on GitHub ${{ matrix.issues.url }}
diff --git a/.github/workflows/monitor-issues-in-voting.yaml b/.github/workflows/monitor-issues-in-voting.yaml
new file mode 100644
index 00000000000..929310d1475
--- /dev/null
+++ b/.github/workflows/monitor-issues-in-voting.yaml
@@ -0,0 +1,58 @@
+name: Monitor issues on voting status
+#Runs the first day of each month at 10 A.M
+on:
+ schedule:
+ - cron: "0 10 1 * *"
+
+jobs:
+ select-top-voted-issue:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - uses: actions/checkout@v3
+ - name: Select the most voted enhancement
+ id: select-top-voted-issue
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const script = require('./.github/workflows/scripts/selectMostVotedIssue.js')
+ return await script({github, context, core})
+ - name: Notify MS Teams channel
+ id: notify-ms-teams
+ if: ${{ success() && steps.select-top-voted-issue.outputs.result }}
+ uses: simbo/msteams-message-card-action@latest
+ env:
+ issueTitle: ${{ fromJSON(steps.select-top-voted-issue.outputs.result).title }}
+ issueURL: ${{ fromJSON(steps.select-top-voted-issue.outputs.result).url }}
+ assignee: ${{ fromJSON(steps.select-top-voted-issue.outputs.result).assignee }}
+ with:
+ webhook: ${{ secrets.COMMUNITY_EVENTS_WEBHOOK_URL }}
+ title: An Enhancement Proposal Issue has been selected the top-voted issue!
+ message: ${{ env.issueTitle }}
assigned to ${{ env.assignee }}
+ buttons: |
+ View Issue on GitHub ${{ env.issueURL }}
+ close-forgotten-voting-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ needs: select-top-voted-issue
+ env:
+ maximumVotingThreshold: ${{ vars.MAX_VOTING_THRESHOLD_DAYS }}
+ steps:
+ - name: Check environment
+ run: |
+ if [ -z $maximumVotingThreshold ]; then
+ echo "::error::'MAX_VOTING_THRESHOLD_DAYS' environment variable is not set"
+ exit 1
+ fi
+ - uses: actions/checkout@v3
+ if: success()
+ - name: Close "forgotten" enhancement requests
+ id: close-forgotten-enhancements
+ if: success()
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const script = require('./.github/workflows/scripts/closeForgottenEnhancements.js')
+ script({github, context, core})
diff --git a/.github/workflows/scripts/closeForgottenEnhancements.js b/.github/workflows/scripts/closeForgottenEnhancements.js
new file mode 100644
index 00000000000..0638b3a3e76
--- /dev/null
+++ b/.github/workflows/scripts/closeForgottenEnhancements.js
@@ -0,0 +1,60 @@
+module.exports = async ({github, context, core}) => {
+ const {owner, repo} = context.repo;
+ // Query all GH issues for Voting
+
+ const votingLabel = "Status: Voting";
+ let response = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ labels: votingLabel,
+ state: 'open',
+ });
+ if (response.data.length === 0) {
+ core.debug('No issues marked for voting found. Exiting.');
+ return;
+ }
+ const votingThreshold = process.env.maximumVotingThreshold;
+ const parsedDays = parseFloat(votingThreshold);
+
+ let now = new Date().getTime();
+ for (let issue of response.data) {
+ core.debug(`Processing issue #${issue.number}`);
+ core.debug(`Issue was created ${issue.created_at}`);
+
+ let createdDate = new Date(issue.created_at).getTime();
+ let daysSinceCreated = (now - createdDate) / 1000 / 60 / 60 / 24;
+ let reactions = issue.reactions['+1'];
+
+ core.debug(`Issue +1 reactions count is ${reactions}`);
+
+ if (reactions < 2 && daysSinceCreated > parsedDays) {
+ core.debug(`Closing #${issue.number} because it hasn't received enough votes after ${parsedDays} days`);
+
+ const message = `Greetings,
+ This issue has been open for community voting for more than ${parsedDays} days and sadly it hasn't received enough votes to be considered for its implementation according to our community policies.
+ As there is not enough interest from the community we'll proceed to close this issue.`;
+
+ await github.rest.issues.createComment({
+ owner : owner,
+ repo : repo,
+ issue_number: issue.number,
+ body: message
+ });
+
+ await github.rest.issues.update({
+ owner: owner,
+ repo: repo,
+ issue_number: issue.number,
+ labels: [],
+ state: 'closed'
+ });
+
+ await github.rest.issues.lock({
+ owner : owner,
+ repo : repo,
+ issue_number : issue.number,
+ lock_reason : 'resolved'
+ });
+ }
+ }
+}
diff --git a/.github/workflows/scripts/filterInactiveIssues.js b/.github/workflows/scripts/filterInactiveIssues.js
new file mode 100644
index 00000000000..33923b2532c
--- /dev/null
+++ b/.github/workflows/scripts/filterInactiveIssues.js
@@ -0,0 +1,43 @@
+module.exports = async ({github, context, core}) => {
+ const { owner, repo } = context.repo;
+ const openLabel = "Status: Open";
+
+ const parsedDays = process.env.inactiveIntervalDays;
+ const thresholdInMillis = parsedDays * 24 * 60 * 60 * 1000;
+
+ // Query all GH issues that are open
+ const response = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ labels: openLabel,
+ state: "open",
+ });
+ core.debug(`Inactive interval days is set to ${parsedDays}`);
+
+ let inactiveIssues = [];
+ for(let issue of response.data){
+ //Get issue events, which are returned by creation date in descending order
+ const eventResponse = await github.rest.issues.listEvents({
+ owner,
+ repo,
+ issue_number : issue.number
+ });
+ //Filter which events correspond to the 'labeled' event in which the `Status: Open` label was added
+ let lastOpenEvent = eventResponse.data.filter((event) => event.event === 'labeled' && event.label.name === openLabel)[0];
+
+ //If the event date is beyond the threshold date, the issue is added to the result array
+ if((new Date().getTime() - new Date(lastOpenEvent.created_at).getTime()) > thresholdInMillis){
+ inactiveIssues.push({
+ number : issue.number,
+ title : issue.title,
+ url: issue.html_url,
+ assignee: issue.assignees.length ? issue.assignees[0].login : '???'
+ });
+ }
+ }
+
+ core.debug(`${inactiveIssues.length} issues detected to be inactive`);
+ if (inactiveIssues.length > 0) {
+ return inactiveIssues;
+ }
+}
diff --git a/.github/workflows/scripts/selectMostVotedIssue.js b/.github/workflows/scripts/selectMostVotedIssue.js
new file mode 100644
index 00000000000..440b5372b44
--- /dev/null
+++ b/.github/workflows/scripts/selectMostVotedIssue.js
@@ -0,0 +1,76 @@
+module.exports = async ({github, context, core}) => {
+ let { owner, repo } = context.repo;
+
+ const openLabel = "Status: Open";
+ const votingLabel = "Status: Voting";
+
+ // Query all GH issues for Voting
+ const response = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ labels: votingLabel,
+ state: 'open',
+ direction: 'desc',
+ });
+
+ //response has all the issues labeled with Voting.
+ if (response.data.length === 0) {
+ core.debug('No issues marked for voting found. Exiting.');
+ return;
+ }
+ //filter issues with at least 2 votes.
+ response.data = response.data.filter((issue) => issue.reactions['+1'] > 1)
+ if (response.data.length === 0) {
+ core.debug('No issues with more than 2 votes found. Exiting');
+ return;
+ }
+
+ let mostVotes = 0;
+ let selectedIssue = 0;
+ let oldestDate = null;
+
+ for (const issue of response.data) {
+ core.debug(`Processing issue #${issue.number}`);
+ core.debug(`Number of +1 reactions ${issue.reactions['+1']}`);
+ core.debug(`Issue was created ${issue.created_at}`);
+
+ let votes = issue.reactions['+1'];
+ let createdDate = new Date(issue.created_at).getTime();
+
+ if (oldestDate === null) {
+ oldestDate = createdDate;
+ selectedIssue = issue;
+ mostVotes = votes;
+ }
+ if ((votes >= mostVotes) && (createdDate < oldestDate)) {
+ mostVotes = votes;
+ selectedIssue = issue;
+ }
+ }
+ core.debug(`Highest votes is ${mostVotes}`);
+ core.debug(`Final issue selected for enhancement is #${selectedIssue.number} created on ${selectedIssue.created_at}`);
+
+ let message = `Greetings,
+ This enhancement request has been selected by the Payara Community as the most voted enhancement of this month and
+ thus will be escalated to our product development backlog.`;
+
+ await github.rest.issues.createComment({
+ owner : owner,
+ repo : repo,
+ issue_number: selectedIssue.number,
+ body: message
+ });
+ await github.rest.issues.update({
+ owner : owner,
+ repo : repo,
+ issue_number: selectedIssue.number,
+ labels : [openLabel]
+ });
+
+ return {
+ number : selectedIssue.number,
+ title : selectedIssue.title,
+ url: selectedIssue.html_url,
+ assignee: selectedIssue.assignees.length ? selectedIssue.assignees[0].login : null
+ };
+}