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

feat: prde-1918 Configure review request delegation for teams #49

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
## Internal changes

- Don't remove existing collaborators, teams on sync
- Added support for custom team roles
- Fixed test config, skipped failing unit tests
- Support is_alphanumeric on autolink references
- <strike>Fixed test config, skipped failing unit tests</strike> (Pushed upstream)
- <strike>Support is_alphanumeric on autolink references</strike> (Pushed upstream)
- Added metrics from github api requests
- Ignore archived repos to prevent errors trying to apply settings
- Disabled environments due to https://github.com/github/safe-settings/issues/551
- Disabled validator due to app crash if a validator throws
- Disabled milestones due to unit test indicating it's broken
- Added `reviewRequestDelegation` for teams

[![Create a release](https://github.com/github/safe-settings/actions/workflows/create-release.yml/badge.svg)](https://github.com/github/safe-settings/actions/workflows/create-release.yml)

Expand Down
88 changes: 84 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,31 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
}
}

async function syncTeamSettings (nop, context, repo = context.repo(), ref) {
try {
deploymentConfig = await loadYamlFileSystem()
robot.log.debug(`deploymentConfig is ${JSON.stringify(deploymentConfig)}`)
const configManager = new ConfigManager(context, ref)
const runtimeConfig = await configManager.loadGlobalSettingsYaml()
const config = Object.assign({}, deploymentConfig, runtimeConfig)
robot.log.debug(`config for ref ${ref} is ${JSON.stringify(config)}`)
return Settings.syncTeams(nop, context, repo, config, ref)
} catch (e) {
if (nop) {
let filename = env.SETTINGS_FILE_PATH
if (!deploymentConfig) {
filename = env.DEPLOYMENT_CONFIG_FILE
deploymentConfig = {}
}
const nopcommand = new NopCommand(filename, repo, null, e, 'ERROR')
robot.log.error(`NOPCOMMAND ${JSON.stringify(nopcommand)}`)
Settings.handleError(nop, context, repo, deploymentConfig, ref, nopcommand)
} else {
throw e
}
}
}

/**
* Loads the deployment config file from file system
* Do this once when the app starts and then return the cached value
Expand Down Expand Up @@ -157,6 +182,27 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
return configs
}

function getAllChangedTeamConfigs (payload, owner) {
const settingPattern = new Glob('.github/teams/*.yml')
const added = payload.commits.map(c => {
return (c.added.filter(s => {
robot.log.debug(JSON.stringify(s))
return (s.search(settingPattern) >= 0)
}))
}).flat(2)
const modified = payload.commits.map(c => {
return (c.modified.filter(s => {
robot.log.debug(JSON.stringify(s))
return (s.search(settingPattern) >= 0)
}))
}).flat(2)
const changes = added.concat(modified)
return changes.map(file => {
robot.log.debug(`${JSON.stringify(file)}`)
return { team: file.match(settingPattern)[1], owner }
})
}

function getChangedRepoConfigName (glob, files, owner) {
const modifiedFiles = files.filter(s => {
robot.log.debug(JSON.stringify(s))
Expand Down Expand Up @@ -240,6 +286,13 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
return syncAllSettings(false, context)
}

const teamChanges = getAllChangedTeamConfigs(payload, context.repo().owner)
if (teamChanges.length > 0) {
return Promise.all(teamChanges.map(repo => {
return syncTeamSettings(false, context, repo)
}))
}

const repoChanges = getAllChangedRepoConfigs(payload, context.repo().owner)
if (repoChanges.length > 0) {
return Promise.all(repoChanges.map(repo => {
Expand Down Expand Up @@ -310,8 +363,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
const member_change_events = [
'member',
'team.added_to_repository',
'team.removed_from_repository',
'team.edited'
'team.removed_from_repository'
]

robot.on(member_change_events, async context => {
Expand Down Expand Up @@ -471,17 +523,28 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
return syncAllSettings(true, context, context.repo(), pull_request.head.ref)
}

const teamChanges = getChangedRepoConfigName(new Glob('.github/teams/*.yml'), files, context.repo().owner)
const teamModified = teamChanges.length > 0

const repoChanges = getChangedRepoConfigName(new Glob('.github/repos/*.yml'), files, context.repo().owner)
const repoModified = repoChanges.length > 0

const subOrgChanges = getChangedSubOrgConfigName(new Glob('.github/suborgs/*.yml'), files, context.repo().owner)
const subOrgModified = subOrgChanges.length > 0

if (repoModified && subOrgModified) {
robot.log.debug('Both repo and subOrg change detected, doing a full synch...')
if (
(repoModified && subOrgModified) ||
(repoModified && teamModified) ||
(subOrgModified && teamModified)
) {
robot.log.debug('Changes detected across suborg/repos/teams folders, doing a full synch...')
return syncAllSettings(true, context, context.repo(), pull_request.head.ref)
}

if (teamModified) {
return syncTeamSettings(true, context, context.repo(), pull_request.head.ref)
}

if (repoModified) {
return Promise.all(repoChanges.map(repo => {
return syncSettings(true, context, repo, pull_request.head.ref)
Expand Down Expand Up @@ -515,6 +578,23 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
return syncSettings(false, context)
})

robot.on(['team.created', 'team.edited'], async context => {
robot.log.info(`team.${context.payload.action} event for ${context.payload.team.slug}`)
const repo = {
repo: env.ADMIN_REPO,
owner: context.payload.organization.login,
team: context.payload.team.slug
}

const enhancedContext = {
...context,
// Workaround: this is required deep in configManager
repo: () => repo
}

return syncTeamSettings(false, enhancedContext)
})

if (process.env.CRON) {
/*
# ┌────────────── second (optional)
Expand Down
12 changes: 6 additions & 6 deletions lib/commentmessage.eta
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
* Run on: `<%= new Date() %>`

* Number of repos that were considered: `<%= Object.keys(it.reposProcessed).length %>`
* Number of repos/teams that were considered: `<%= Object.keys(it.reposProcessed).length %>`

### Breakdown of changes
| Repo <% Object.keys(it.changes).forEach(plugin => { %> | <%= plugin %> settings <% }) %> |
| Repo/Team <% Object.keys(it.changes).forEach(plugin => { %> | <%= plugin %> settings <% }) %> |
| -- <% Object.keys(it.changes).forEach(plugin => { -%> | -- <% }) %>
|
|
<% Object.keys(it.reposProcessed).forEach( repo => { -%>
| <%= repo -%>
<%- Object.keys(it.changes).forEach(plugin => { -%>
<%_ if (it.changes[plugin][repo]) { -%> | :hand: <% } else { %> | :grey_exclamation: <% } -%>
<%_ }) -%> |
<% }) -%>

:hand: -> Changes to be applied to the GitHub repository.
:grey_exclamation: -> nothing to be changed in that particular GitHub repository.
:hand: -> Changes to be applied to the GitHub repository/team.
:grey_exclamation: -> nothing to be changed in that particular GitHub repository/team.

### Breakdown of errors

<% if (Object.keys(it.errors).length === 0) { %>
`None`
<% } else { %>
<% Object.keys(it.errors).forEach(repo => { %>
<%_= repo %>:
<%_= repo %>:
<% it.errors[repo].forEach(plugin => { %>
* <%= plugin.msg %>
<% }) %>
Expand Down
8 changes: 5 additions & 3 deletions lib/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ const metrics = require("@operate-first/probot-metrics");
const actionCounter = metrics.useCounter({
name: 'num_of_actions_total',
help: 'Total number of actions received',
labelNames: ['repository', 'result', 'action', 'nop'],
labelNames: ['repository', 'team', 'result', 'action', 'nop'],
});

const meteredPlugin = async (plugin, fn) => {
try {
const result = await fn()
actionCounter
.labels({
repository: plugin.repo.repo,
repository: plugin.repo?.repo,
team: plugin.team?.slug,
nop: plugin.nop,
result: "success",
action: plugin.constructor.name
Expand All @@ -22,7 +23,8 @@ const meteredPlugin = async (plugin, fn) => {
console.dir(e)
actionCounter
.labels({
repository: plugin.repo.repo,
repository: plugin.repo?.repo,
team: plugin.team?.slug,
result: "error",
nop: plugin.nop,
action: plugin.constructor.name
Expand Down
Loading
Loading