Skip to content

Commit

Permalink
Make assorted improvements to automations and workflows (#3443)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvkb authored Dec 5, 2023
1 parent c312886 commit b5270e2
Show file tree
Hide file tree
Showing 23 changed files with 349 additions and 821 deletions.
17 changes: 0 additions & 17 deletions .github/actionlint-matcher.json

This file was deleted.

8 changes: 2 additions & 6 deletions .github/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,19 @@ group:
dest: automations/js/src/project_automation/
- source: automations/js/src/utils/
dest: automations/js/src/utils/
- source: automations/js/src/label_pr.mjs
dest: automations/js/src/label_pr.mjs
# Synced workflows
- source: .github/workflows/issue_automations.yml
dest: .github/workflows/issue_automations.yml
- source: .github/workflows/pr_automations.yml
dest: .github/workflows/pr_automations.yml
- source: .github/workflows/pr_automations_init.yml
dest: .github/workflows/pr_automations_init.yml
- source: .github/workflows/label_new_pr.yml
dest: .github/workflows/label_new_pr.yml
- source: .github/workflows/pr_label_check.yml
dest: .github/workflows/pr_label_check.yml
- source: .github/workflows/pr_ping.yml
dest: .github/workflows/pr_ping.yml
- source: .github/workflows/actionlint.yml
dest: .github/workflows/actionlint.yml
- source: .github/actionlint-matcher.json
dest: .github/actionlint-matcher.json
- source: .github/workflows/subscribe_to_label.yml
dest: .github/workflows/subscribe_to_label.yml
- source: .github/subscribe-to-label.json
Expand Down
24 changes: 0 additions & 24 deletions .github/workflows/actionlint.yml

This file was deleted.

34 changes: 0 additions & 34 deletions .github/workflows/label_new_pr.yml

This file was deleted.

33 changes: 0 additions & 33 deletions .github/workflows/label_pr.yml

This file was deleted.

19 changes: 6 additions & 13 deletions .github/workflows/label_sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ on:
schedule:
- cron: "0 0 * * *" # at 00:00

env:
LOGGING_LEVEL: 20 # corresponds to INFO
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}

jobs:
sync_labels:
name: Sync labels
Expand All @@ -18,13 +14,10 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup CI env
uses: ./.github/actions/setup-env
- name: Sync labels from monorepo to infra
uses: actions/github-script@v7
with:
setup_nodejs: false # Node.js is not needed to run Python automations.
install_recipe: "automations/python/install"

- name: Sync standard labels
working-directory: ./automations/python
run: |
pipenv run python sync_labels.py
github-token: ${{ secrets.ACCESS_TOKEN }}
script: |
const { main } = await import('${{ github.workspace }}/automations/js/src/sync_labels.mjs')
await main(github, core)
10 changes: 9 additions & 1 deletion .github/workflows/pr_automations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,18 @@ jobs:
unzip event_info.zip
mv event.json /tmp/event.json
- name: Perform PR labelling
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ACCESS_TOKEN }}
script: |
const { main } = await import('${{ github.workspace }}/automations/js/src/label_pr.mjs')
await main(github)
- name: Perform PR automations
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ACCESS_TOKEN }}
script: |
const { main } = await import('${{ github.workspace }}/automations/js/src/project_automation/prs.mjs')
await main(github)
await main(github, core)
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ api/docs/_templates/page.html
# Django templates
api/api/templates

# One-time scripts
utilities/migrate_issues/

# Autogenerated
pnpm-lock.yaml
119 changes: 119 additions & 0 deletions automations/js/src/label_pr.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { readFileSync } from 'fs'
import { PullRequest } from './utils/pr.mjs'
import { IdSet } from './utils/id_set.mjs'

const exactlyOne = ['priority', 'goal']
const atleastOne = ['aspect']
const atleastOneCheckOnly = ['stack']

/**
* Check if the list of labels covers all requirements.
*
* @param labels {import('./utils/pr.mjs').Label[]} the list of labels
* @returns {boolean} whether the list of labels covers all requirements
*/
function getIsFullyLabeled(labels) {
for (let req of exactlyOne) {
if (labels.filter((label) => label.name.includes(req)).length !== 1) {
return false
}
}
for (let req of atleastOne + atleastOneCheckOnly) {
if (labels.filter((label) => label.name.includes(req)).length < 1) {
return false
}
}
return true
}

/**
* Apply labels to a PR based on the PR's linked issues.
*
* Note that this function does not concern itself with the management of stack
* labels as that is performed by a job in the CI + CD workflow.
*
* @param octokit {import('@octokit/rest').Octokit} the Octokit instance to use
* @param core {import('@actions/core')} GitHub Actions toolkit, for logging
*/
export const main = async (octokit, core) => {
const { eventName, eventAction, prNodeId } = JSON.parse(
readFileSync('/tmp/event.json', 'utf-8')
)

if (
eventName !== 'pull_request' ||
!['opened', 'edited'].includes(eventAction)
) {
core.info(
`Event "${eventName}"/"${eventAction}" is not an event where a PR should be labelled.`
)
return
}

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

let isTriaged = false
if (pr.labels && pr.labels.some((label) => !label.name.includes('stack'))) {
// If a PR has non-stack labels, it has likely been triaged by a maintainer.
core.info('The PR already has non-stack labels.')
isTriaged = true
}

// The logic for labelling a PR is as follows.
const finalLabels = new IdSet()

// We start with the PRs current labels. We do not remove any labels already
// set as they could be the work of the CI labeller job or a maintainer.
pr.labels.forEach((label) => {
core.debug(`Adding label "${label.name}" from PR.`)
finalLabels.add(label)
})

// Then we compile all the labels of all the linked issues into a pool. This
// will be used to find the labels that satisfy the requirements.
const labelPool = pr.linkedIssues.flatMap((issue) => issue.labels)
core.debug(`Label pool: ${labelPool}`)

// For each label that we only need one of, we check if the PR already has
// such a label. If not, we check if the label pool contains any valid labels
// and add the first one we find.
for (let rule of exactlyOne) {
if (finalLabels.items.some((label) => label.name.includes(rule))) {
core.info(`PR already has a "${rule}" label.`)
continue
}
const validLabel = labelPool.find((label) => label.name.includes(rule))
if (validLabel) {
core.info(`Adding label "${validLabel.name}" to PR.`)
finalLabels.add(validLabel)
}
}

// For each label that we need at least one of, we add all the valid labels
// from the label pool. Our ID set implementation will weed out duplicates.
for (let rule of atleastOne) {
const validLabels = labelPool.filter((label) => label.name.includes(rule))
core.info(`Adding labels "${validLabels}" to PR.`)
validLabels.forEach((label) => {
finalLabels.add(label)
})
}

// We check if the label is fully labeled. If not, we add the appropriate
// label to get the maintainers' attention.
if (!getIsFullyLabeled(finalLabels.items)) {
let attnLabel
if (isTriaged) {
attnLabel = '🏷 status: label work required'
} else {
attnLabel = '🚦 status: awaiting triage'
}
core.info(`Pull not fully labelled so adding "${attnLabel}".`)
finalLabels.add(attnLabel)
}

// Finally we commit all label IDs to the PR via a mutation. GitHub will only
// add the new IDs we provide, no existing label will be changed.
await pr.addLabels(Array.from(finalLabels.ids))
}
2 changes: 1 addition & 1 deletion automations/js/src/last_week_tonight.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import yaml from 'js-yaml'
import axios from 'axios'
import { Octokit } from '@octokit/rest'

import { escapeHtml } from './html.mjs'
import { escapeHtml } from './utils/html.mjs'

/* Environment variables */

Expand Down
7 changes: 4 additions & 3 deletions automations/js/src/project_automation/prs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async function syncReviews(pr, prBoard, prCard) {
*/
async function syncIssues(pr, backlogBoard, destColumn) {
for (let linkedIssue of pr.linkedIssues) {
const issueCard = await backlogBoard.addCard(linkedIssue)
const issueCard = await backlogBoard.addCard(linkedIssue.id)
await backlogBoard.moveCard(issueCard.id, backlogBoard.columns[destColumn])
}
}
Expand All @@ -43,13 +43,14 @@ async function syncIssues(pr, backlogBoard, destColumn) {
* 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
*/
export const main = async (octokit) => {
export const main = async (octokit, core) => {
const { eventName, eventAction, prNodeId } = JSON.parse(
readFileSync('/tmp/event.json', 'utf-8')
)

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

const prBoard = await getBoard(octokit, 'PRs')
Expand Down
Loading

0 comments on commit b5270e2

Please sign in to comment.