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

Make assorted improvements to automations and workflows #3443

Merged
merged 16 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
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 to infra
dhruvkb marked this conversation as resolved.
Show resolved Hide resolved
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)
dhruvkb marked this conversation as resolved.
Show resolved Hide resolved
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
117 changes: 117 additions & 0 deletions automations/js/src/label_pr.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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('This is not an event where a PR should be labelled.')
dhruvkb marked this conversation as resolved.
Show resolved Hide resolved
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
Loading