diff --git a/.github/workflows/schedule-monthly.yml b/.github/workflows/schedule-monthly.yml new file mode 100644 index 0000000000..f43b748302 --- /dev/null +++ b/.github/workflows/schedule-monthly.yml @@ -0,0 +1,45 @@ +name: Schedule Monthly + +on: + schedule: + - cron: "0 8 1 * *" + +jobs: + list-inactive-members: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + # gets a list of website-write team members with no open issues, returns a list of member's github handles + - name: Get List + uses: actions/github-script@v6 + id: get-list + with: + github-token: ${{ secrets.HACKFORLA_BOT_PA_TOKEN }} + script: | + const script = require('./github-actions/trigger-schedule/list-inactive-members/get-list.js'); + const getList = script({g: github, c: context}); + return getList; + + # creates a new issue in hackforla/website repo with the list populated above. creates a project card linking to this issue + - name: Create New Issue + uses: actions/github-script@v6 + id: create-new-issue + with: + github-token: ${{ secrets.HACKFORLA_BOT_PA_TOKEN }} + script: | + const script = require('./github-actions/trigger-schedule/list-inactive-members/create-new-issue.js'); + const list = ${{ steps.get-list.outputs.result }}; + const createNewIssue = script({g: github, c: context}, list); + return createNewIssue; + + # comments on issue #2607, notifying leads that the above issue has been created + - name: Comment Issue + uses: actions/github-script@v6 + id: comment-issue + with: + github-token: ${{ secrets.HACKFORLA_BOT_PA_TOKEN }} + script: | + const script = require('./github-actions/trigger-schedule/list-inactive-members/comment-issue.js'); + const newIssueNumber = ${{ steps.create-new-issue.outputs.result }}; + script({g: github, c: context}, newIssueNumber); diff --git a/github-actions/trigger-schedule/list-inactive-members/comment-issue.js b/github-actions/trigger-schedule/list-inactive-members/comment-issue.js new file mode 100644 index 0000000000..71543569cc --- /dev/null +++ b/github-actions/trigger-schedule/list-inactive-members/comment-issue.js @@ -0,0 +1,26 @@ +// Import modules + +// Global variables +var github; +var context; + +async function main({ g, c }, newIssueNumber) { + github = g; + context = c; + + let agendaAndNotesIssueNumber = 2607; + await commentOnIssue(agendaAndNotesIssueNumber, newIssueNumber); +} + +const commentOnIssue = async (agendaAndNotesIssueNumber, newIssueNumber) => { + const owner = "hackforla"; + const repo = "website"; + await github.rest.issues.createComment({ + owner, + repo, + issue_number: agendaAndNotesIssueNumber, + body: `**Review Inactive Members:** #${newIssueNumber}`, + }); +}; + +module.exports = main; diff --git a/github-actions/trigger-schedule/list-inactive-members/create-new-issue.js b/github-actions/trigger-schedule/list-inactive-members/create-new-issue.js new file mode 100644 index 0000000000..6299d181a6 --- /dev/null +++ b/github-actions/trigger-schedule/list-inactive-members/create-new-issue.js @@ -0,0 +1,82 @@ +// Import modules + +// Global variables +var github; +var context; + +async function main({ g, c }, list) { + github = g; + context = c; + + const owner = "hackforla"; + const repo = "website"; + + // create a new issue in repo, return the issue id for later: creating the project card linked to this issue + const issue = await createIssue(owner, repo, list); + const issueId = issue.id; + const issueNumber = issue.number; + // get project id, in order to get the column id of `New Issue Approval` in `Project Board` + const projectId = await getProjectId(owner, repo); + // get column id, in order to create a project card in `Project Board` and place in `New Issue Approval` + const columnId = await getColumnId(projectId); + // create the project card, which links to the issue created on line 16 + await createProjectCard(issueId, columnId); + // return issue number: going to be using this number to link the issue when commenting on the `Dev/PM Agenda and Notes` + return issueNumber; +} + +const createIssue = async (owner, repo, list) => { + let listWithNewLine = list.join("\n"); + let body = "**Inactive Members:**\n\n" + listWithNewLine; + let labels = [ + "Ready for dev lead", + "Ready for product", + "Complexity: Small", + "Size: 0.5pt", + ]; + const title = "Review Inactive Members"; + const issue = await github.rest.issues.create({ + owner, + repo, + title, + body, + labels, + }); + return issue.data; +}; + +const getProjectId = async (owner, repo) => { + // get all projects for the repo + let projects = await github.rest.projects.listForRepo({ + owner, + repo, + }); + // select project with name `Project Board`, access the `id` + let projectId = projects.data.filter((project) => { + return (project.name = "Project Board"); + })[0].id; + return projectId; +}; + +const getColumnId = async (projectId) => { + // get all columns in the project board + let columns = await github.rest.projects.listColumns({ + project_id: projectId, + }); + // select column with name `New Issue Approval`, access the `id + let columnId = columns.data.filter((column) => { + return column.name === "New Issue Approval"; + })[0].id; + return columnId; +}; + +const createProjectCard = async (issueId, columnId) => { + const card = await github.rest.projects.createCard({ + column_id: columnId, + content_id: issueId, + content_type: "Issue", + }); + return card.data; +}; + +module.exports = main; diff --git a/github-actions/trigger-schedule/list-inactive-members/get-list.js b/github-actions/trigger-schedule/list-inactive-members/get-list.js new file mode 100644 index 0000000000..a2a6644e02 --- /dev/null +++ b/github-actions/trigger-schedule/list-inactive-members/get-list.js @@ -0,0 +1,69 @@ +// Import modules + +// Global variables +var github; +var context; + +async function main({ g, c }) { + github = g; + context = c; + + const org = "hackforla"; + const teamSlug = "website-write"; + const owner = "hackforla"; + const repo = "website"; + + // get number of team members for the website-write team (need this number to determine amount of page numbers to fetch from Github API) + // Github API limits 100 max results per request + let pageNumbers = await getNumberOfPages(org, teamSlug, github); + let allTeamMembers = await getAllMembers(org, teamSlug, pageNumbers); + + return await selectMembersWithNoIssues(allTeamMembers, owner, repo); +} + +const getNumberOfPages = async (org, teamSlug) => { + // get number of pages, needed for `getMembersWithoutIssues` function. GithubAPI has a return limit of 100 results => over 300 team members in website-write + let websiteWriteTeam = await github.rest.teams.getByName({ + org, + team_slug: teamSlug, + }); + + let membersCount = websiteWriteTeam.data.members_count; + let pageNumbers = Math.ceil(membersCount / 100); + return pageNumbers; +}; + +const getAllMembers = async (org, teamSlug, pageNumbers) => { + // get all team members + let allTeamMembers = []; + for (let currPage = 1; currPage <= pageNumbers; currPage += 1) { + let teamMembers = await github.rest.teams.listMembersInOrg({ + org, + team_slug: teamSlug, + per_page: 100, + page: currPage, + }); + allTeamMembers = allTeamMembers.concat(teamMembers.data); + } + return allTeamMembers; +}; + +const selectMembersWithNoIssues = async (allTeamMembers, owner, repo) => { + // select team members without open issues + let inactiveMembers = []; + for (let member of allTeamMembers) { + let assignee = member.login; + let memberIssues = await github.rest.issues.listForRepo({ + owner, + repo, + state: "open", + assignee, + }); + if (memberIssues.data.length === 0) { + inactiveMembers.push(assignee); + } + } + return inactiveMembers; +}; + +module.exports = main;