Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logging to project automations #3404

Merged
merged 19 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e356aab
Add detailed `console.log` statements for enhanced debugging in GitHu…
Proibito04 Nov 27, 2023
680d771
temp update of console.log
Proibito04 Nov 28, 2023
77b75a6
Refactor logging to use `@actions/core` and remove redundant logs
Proibito04 Nov 28, 2023
ce70e6f
Remove `debug` import from issues.mjs
Proibito04 Nov 28, 2023
723183f
Merge branch 'main' into issue_3396
Proibito04 Nov 28, 2023
f1d96e9
Merge branches 'issue_3396' and 'issue_3396' of github.com:Proibito04…
Proibito04 Nov 28, 2023
c05900f
Merge branch 'main' into issue_3396
Proibito04 Nov 30, 2023
df83e81
Pass `core` to from YAML to `main` to `Project`
dhruvkb Dec 1, 2023
8742c21
Use `this.core` to log info, error and debug lines
dhruvkb Dec 1, 2023
e2394a9
Refactored Project class, updated moveCard and YAML files
Proibito04 Dec 2, 2023
ed58894
Applied suggested changes in issue.mjs and projects.mjs.
Proibito04 Dec 2, 2023
ec5fea3
Merge branch 'main' into issue_3396
Proibito04 Dec 4, 2023
917929e
Merge branch 'main' into issue_3396
Proibito04 Dec 5, 2023
9556346
Applied suggested changes in issue.mjs and projects.mjs.
Proibito04 Dec 11, 2023
12a56a9
Merge branch 'issue_3396' of github.com:Proibito04/openverse into iss…
Proibito04 Dec 11, 2023
51cccf6
Merge branch 'main' of https://github.com/WordPress/openverse into is…
dhruvkb Dec 14, 2023
b99d5ed
Add more logging
dhruvkb Dec 14, 2023
e7ecf6c
Dedent code and increase logging
dhruvkb Dec 14, 2023
9c58a2d
Add more `info` and `debug` logs
dhruvkb Dec 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/issue_automations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
github-token: ${{ secrets.ACCESS_TOKEN }}
script: |
const { main } = await import('${{ github.workspace }}/automations/js/src/project_automation/issues.mjs')
await main(github, context)
await main(github, core, context)
47 changes: 32 additions & 15 deletions automations/js/src/project_automation/issues.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,77 @@ import { getBoard } from '../utils/projects.mjs'
* Set the "Priority" custom field based on the issue's labels. Also move
* the card for critical issues directly to the "📅 To Do" column.
*
* @param issue {import('@octokit/rest')}
* @param board {import('../utils/projects.mjs').Project}
* @param card {import('../utils/projects.mjs').Card}
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
* @param issue {Issue} the issue for which to set the "Priority" custom field
* @param backlogBoard {Project} the project board for issues
* @param issueCard {Card} the card for the issue to sync
*/
async function syncPriority(issue, board, card) {
async function syncPriority(core, issue, backlogBoard, issueCard) {
core.info(`Syncing priority for issue "${issue.number}".`)

const priority = issue.labels.find((label) =>
label.name.includes('priority')
)?.name
core.debug(`Priority: ${priority}`)

if (priority) {
await board.setCustomChoiceField(card.id, 'Priority', priority)
await backlogBoard.setCustomChoiceField(issueCard.id, 'Priority', priority)
}
if (priority === '🟥 priority: critical') {
await board.moveCard(card.id, board.columns.ToDo)
core.info('Moving critical issue to "📅 To Do" column.')
await backlogBoard.moveCard(issueCard.id, backlogBoard.columns.ToDo)
}
}

/**
* This is the entrypoint of the script.
*
* @param octokit {import('@octokit/rest').Octokit} the Octokit instance to use
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
* @param context {import('@actions/github').context} info about the current event
*/
export const main = async (octokit, context) => {
export const main = async (octokit, core, context) => {
core.info('Starting script `issues.mjs`.')

const { EVENT_ACTION: eventAction } = process.env
core.debug(`Event action: ${eventAction}`)

const issue = context.payload.issue
core.debug(`Issue node ID: ${issue.node_id}`)

const label = context.payload.label
core.info('Issue details:', issue)

if (issue.labels.some((label) => label.name === '🧭 project: thread')) {
// Do not add project threads to the Backlog board.
process.exit(0)
core.warning('Issue is a project thread. Exiting.')
Proibito04 marked this conversation as resolved.
Show resolved Hide resolved
return
}

const backlogBoard = await getBoard(octokit, 'Backlog')
const backlogBoard = await getBoard(octokit, core, 'Backlog')

// Create new, or get the existing, card for the current issue.
const card = await backlogBoard.addCard(issue.node_id)
core.debug(`Issue card ID: ${card.id}`)

switch (eventAction) {
case 'opened':
case 'reopened': {
if (issue.labels.some((label) => label.name === '⛔ status: blocked')) {
core.info('Issue was opened, labelled as blocked.')
await backlogBoard.moveCard(card.id, backlogBoard.columns.Blocked)
} else {
await backlogBoard.moveCard(card.id, backlogBoard.columns.Backlog)
}

await syncPriority(issue, backlogBoard, card)
await syncPriority(core, issue, backlogBoard, card)
break
}

case 'closed': {
if (issue.state_reason === 'completed') {
core.info('Issue was closed as completed.')
await backlogBoard.moveCard(card.id, backlogBoard.columns.Done)
} else {
core.info('Issue was closed as discarded.')
await backlogBoard.moveCard(card.id, backlogBoard.columns.Discarded)
}
break
Expand All @@ -73,18 +89,19 @@ export const main = async (octokit, context) => {

case 'labeled': {
if (label.name === '⛔ status: blocked') {
core.info('Issue was labeled as blocked.')
await backlogBoard.moveCard(card.id, backlogBoard.columns.Blocked)
}
await syncPriority(issue, backlogBoard, card)
await syncPriority(core, issue, backlogBoard, card)
break
}

case 'unlabeled': {
if (label.name === '⛔ status: blocked') {
// TODO: Move back to the column it came from.
core.info('Issue was unlabeled as blocked.')
await backlogBoard.moveCard(card.id, backlogBoard.columns.Backlog)
}
await syncPriority(issue, backlogBoard, card)
await syncPriority(core, issue, backlogBoard, card)
break
}
}
Expand Down
57 changes: 41 additions & 16 deletions automations/js/src/project_automation/prs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@ import { PullRequest } from '../utils/pr.mjs'
/**
* Move the PR to the right column based on the number of reviews.
*
* @param pr {PullRequest}
* @param prBoard {Project}
* @param prCard {Card}
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
* @param pr {PullRequest} the PR to sync with the reviews and decision
* @param prBoard {Project} the project board for PRs
* @param prCard {Card} the card for the PR to sync
*/
async function syncReviews(pr, prBoard, prCard) {
async function syncReviews(core, pr, prBoard, prCard) {
core.info(`Synchronizing reviews for PR ${pr.nodeId}.`)

const reviewDecision = pr.reviewDecision
const reviewCounts = pr.reviewCounts
core.debug(`PR review counts: ${reviewCounts}`)
core.debug(`PR reviews decision: ${reviewDecision}`)

if (reviewDecision === 'APPROVED') {
core.info('Moving PR on the basis of review decision.')
await prBoard.moveCard(prCard.id, prBoard.columns.Approved)
} else if (reviewDecision === 'CHANGES_REQUESTED') {
core.info('Moving PR on the basis of review decision.')
await prBoard.moveCard(prCard.id, prBoard.columns.ChangesRequested)
} else if (reviewCounts.APPROVED === 1) {
core.info('Moving PR on the basis of 1 approval.')
await prBoard.moveCard(prCard.id, prBoard.columns.Needs1Review)
} else {
await prBoard.moveCard(prCard.id, prBoard.columns.Needs2Reviews)
Expand All @@ -28,13 +36,21 @@ async function syncReviews(pr, prBoard, prCard) {
/**
* Move all linked issues to the specified column.
*
* @param pr {PullRequest}
* @param backlogBoard {Project}
* @param destColumn {string}
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
* @param pr {PullRequest} the PR to sync with the reviews and decision
* @param backlogBoard {Project} the project board for issues
* @param destColumn {string} the destination column where to move the issue
*/
async function syncIssues(pr, backlogBoard, destColumn) {
async function syncIssues(core, pr, backlogBoard, destColumn) {
core.info(`Synchronizing issues for PR ${pr.nodeId}.`)

for (let linkedIssue of pr.linkedIssues) {
core.info(`Syncing issue ${linkedIssue.id}.`)

// Create new, or get the existing, card for the current issue.
const issueCard = await backlogBoard.addCard(linkedIssue.id)
core.debug(`Issue card ID: ${issueCard.id}`)

await backlogBoard.moveCard(issueCard.id, backlogBoard.columns[destColumn])
}
}
Expand All @@ -46,36 +62,44 @@ async function syncIssues(pr, backlogBoard, destColumn) {
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
*/
export const main = async (octokit, core) => {
core.info('Starting script `prs.mjs`.')

const { eventName, eventAction, prNodeId } = JSON.parse(
readFileSync('/tmp/event.json', 'utf-8')
)
core.debug(`Event name: ${eventName}`)
core.debug(`Event action: ${eventAction}`)
core.debug(`PR node ID: ${prNodeId}`)

const pr = new PullRequest(octokit, core, prNodeId)
await pr.init()

const prBoard = await getBoard(octokit, 'PRs')
const backlogBoard = await getBoard(octokit, 'Backlog')
const prBoard = await getBoard(octokit, core, 'PRs')
const backlogBoard = await getBoard(octokit, core, 'Backlog')

// Create new, or get the existing, card for the current pull request.
const prCard = await prBoard.addCard(pr.nodeId)
core.debug(`PR card ID: ${prCard.id}`)

if (eventName === 'pull_request_review') {
await syncReviews(pr, prBoard, prCard)
await syncReviews(core, pr, prBoard, prCard)
} else {
switch (eventAction) {
case 'opened':
case 'reopened': {
if (pr.isDraft) {
core.info('PR is a draft.')
await prBoard.moveCard(prCard.id, prBoard.columns.Draft)
} else {
await syncReviews(pr, prBoard, prCard)
core.info('PR is ready for review.')
await syncReviews(core, pr, prBoard, prCard)
}
await syncIssues(pr, backlogBoard, 'InProgress')
await syncIssues(core, pr, backlogBoard, 'InProgress')
break
}

case 'edited': {
await syncIssues(pr, backlogBoard, 'InProgress')
await syncIssues(core, pr, backlogBoard, 'InProgress')
break
}

Expand All @@ -85,13 +109,14 @@ export const main = async (octokit, core) => {
}

case 'ready_for_review': {
await syncReviews(pr, prBoard, prCard)
await syncReviews(core, pr, prBoard, prCard)
break
}

case 'closed': {
if (!pr.isMerged) {
await syncIssues(pr, backlogBoard, 'Backlog')
core.info('PR was closed without merge.')
await syncIssues(core, pr, backlogBoard, 'Backlog')
}
break
}
Expand Down
3 changes: 2 additions & 1 deletion automations/js/src/utils/pr.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class PullRequest {
id: this.nodeId,
}
)
this.core.debug(`getPrDetails response: ${JSON.stringify(res, null, 2)}`)
const pr = res.node
return {
isMerged: pr.merged,
Expand Down Expand Up @@ -137,7 +138,7 @@ export class PullRequest {
labelIds,
}
)
this.core.debug('addLabels response:', JSON.stringify(res))
this.core.debug(`addLabels response: ${JSON.stringify(res, null, 2)}`)
return res.addLabelsToLabelable.labelable.labels.nodes
}

Expand Down
32 changes: 22 additions & 10 deletions automations/js/src/utils/projects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ class Project {
* owner number
*
* @param octokit {import('@octokit/rest').Octokit} the Octokit instance to use
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
* @param owner {string} the login of the owner (org) of the project
* @param number {number} the number of the project
*/
constructor(octokit, owner, number) {
constructor(octokit, core, owner, number) {
this.octokit = octokit

this.core = core
this.owner = owner
this.number = number
}
Expand Down Expand Up @@ -96,6 +97,7 @@ class Project {
number: this.number,
}
)
this.core.debug(`getProjectId response: ${JSON.stringify(res, null, 2)}`)
const project = res.organization.projectV2
return {
projectId: project.id,
Expand Down Expand Up @@ -126,6 +128,7 @@ class Project {
* @returns {Promise<Card>} the info of the added card
*/
async addCard(issueId) {
this.core.info(`Adding card for issue/PR "${issueId}".`)
const res = await this.octokit.graphql(
`mutation addCard($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: {
Expand All @@ -147,6 +150,7 @@ class Project {
contentId: issueId,
}
)
this.core.debug(`addCard response: ${JSON.stringify(res, null, 2)}`)
const card = res.addProjectV2ItemById.item
return {
id: card.id,
Expand All @@ -163,18 +167,23 @@ class Project {
* @returns {Promise<string>} the ID of the card that was updated
*/
async setCustomChoiceField(cardId, fieldName, optionName) {
this.core.info(
`Setting field "${fieldName}" to value "${optionName}" for card "${cardId}".`
)
Proibito04 marked this conversation as resolved.
Show resolved Hide resolved
// Preliminary validation
if (!this.fields[fieldName]) {
throw new Error(`Unknown field name "${fieldName}".`)
const msg = `Unknown field name "${fieldName}".`
this.core.error(msg)
Proibito04 marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(msg)
}
if (!this.fields[fieldName].options[optionName]) {
throw new Error(
`Unknown option name "${optionName}" for field "${fieldName}".`
)
const msg = `Unknown option name "${optionName}" for field "${fieldName}".`
this.core.error(msg)
throw new Error(msg)
}

const res = await this.octokit.graphql(
`mutation setCustomField($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
`mutation setCustomChoiceField($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId,
itemId: $itemId,
Expand All @@ -193,6 +202,7 @@ class Project {
optionId: this.fields[fieldName].options[optionName],
}
)
this.core.debug('setCustomChoiceField response:', JSON.stringify(res))
Proibito04 marked this conversation as resolved.
Show resolved Hide resolved
return res.updateProjectV2ItemFieldValue.projectV2Item.id
}

Expand All @@ -205,6 +215,7 @@ class Project {
* @returns {Promise<string>} the ID of the card that was moved
*/
async moveCard(cardId, destColumn) {
this.core.info(`Moving card "${cardId}" to column "${destColumn}".`)
Proibito04 marked this conversation as resolved.
Show resolved Hide resolved
return await this.setCustomChoiceField(cardId, 'Status', destColumn)
}
}
Expand All @@ -213,16 +224,17 @@ class Project {
* Get the `Project` instance for the project board with the given name.
*
* @param octokit {import('@octokit/rest').Octokit} the Octokit instance to use
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
* @param name {string} the name of the project (without the 'Openverse' prefix)
* @returns {Project} the `Project` instance to interact with the project board
* @returns {Promise<Project>} the `Project` instance to interact with the project board
*/
export async function getBoard(octokit, name) {
export async function getBoard(octokit, core, name) {
const projectNumber = PROJECT_NUMBERS[name]
if (!projectNumber) {
throw new Error(`Unknown project board "${name}".`)
}

const project = new Project(octokit, 'WordPress', projectNumber)
const project = new Project(octokit, core, 'WordPress', projectNumber)
await project.init()
return project
}