-
Notifications
You must be signed in to change notification settings - Fork 800
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Repo Gardening: allow escalating issues to multiple groups (#39973)
* Repo Gardening: allow escalating issues to multiple groups Up until now, once we had sent one Slack notification about an issue, we couldn't send another to a different team. This was problematic given that we attempt to warn about escalated issues to 2 different teams: - Product Ambassador HEs in the Triage Issues task - Dev teams in the Update Board task By splitting things into 2 different labels ("[Status] Priority Review Triggered" for dev teams, "[Status] Escalated to Product Ambassadors" for Product ambassadors), we can now send notifications to two different teams. It is not the perfect solution since ideally, we'd want to allow notifications per Slack channel instead of per team, but: - There is currently no better way to warn about an issue already escalated than to add a label. - We cannot really use the Slack channel ID in the label name, that would be odd. * Simplify check before we send slack notification This was added in #30100, but it ends up blocking legitimate messages from being sent. * Move checking for labels out of the Slack notification function This should be handled by each task calling the function, for more flexibility, and a simpler notification function. * Remove Update Board task We'll merge it into the existing triage issues task. It is not very intuitive to have this being two different tasks, when both perform actions that can be described as issue triage. It also makes the codebase more approachable, and will allow us to extract / centralize some of the logic that was used in both tasks. * Extract more label detection functions * Extract AI labeling into its own file It should make it a bit easier for folks to contribute. * Add new Project board management "sub-task" for triageIssues * Document the new actions performed by the Triage issues task * Extract method used to find the priority of an issue * Bring it all together in the update triageIssues task * Fix argument order * Add missing argument * Remove unnecessary state check (we check earlier) & add logging * Remove files that are no longer used * Add label when issue is automatically triaged to project board See pfVjQF-55-p2#comment-119 * Update label name See pfVjQF-55-p2#comment-127 * Switch to major version bump See #39973 (comment) * Update to new label value format See #39973 (comment) * Simplify check for existing labels on issues See #39973 (comment) This is similar to what was already done in 148b28c * Better clarify where issue priority comes from, and use it Let's not attempt to add a label to an issue when it is already on the issue. See #39973 (comment)
- Loading branch information
Showing
14 changed files
with
492 additions
and
487 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
projects/github-actions/repo-gardening/changelog/rm-update-board-task
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: major | ||
Type: changed | ||
|
||
Board Triage: remove updateBoard task. It will now be part of the existing triageIssues task. |
4 changes: 4 additions & 0 deletions
4
projects/github-actions/repo-gardening/changelog/update-escalate-issue-multiple
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: patch | ||
Type: changed | ||
|
||
Issue escalation: allow escalating the issue to multiple teams. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
projects/github-actions/repo-gardening/src/tasks/triage-issues/ai-labeling.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
const { getInput } = require( '@actions/core' ); | ||
const debug = require( '../../utils/debug' ); | ||
const getAvailableLabels = require( '../../utils/labels/get-available-labels' ); | ||
const getLabels = require( '../../utils/labels/get-labels' ); | ||
const sendOpenAiRequest = require( '../../utils/openai/send-request' ); | ||
|
||
/* global GitHub, WebhookPayloadIssue */ | ||
|
||
/** | ||
* Request a list of matching labels from Open AI that can be applied to the issue, | ||
* based on the issue contents. | ||
* | ||
* @param {GitHub} octokit - Initialized Octokit REST client. | ||
* @param {string} owner - Repository owner. | ||
* @param {string} repo - Repository name. | ||
* @param {string} title - Issue title. | ||
* @param {string} body - Issue body. | ||
* | ||
* @return {Promise<Object>} Promise resolving to an object of labels to apply to the issue, and their explanations. | ||
*/ | ||
async function fetchOpenAiLabelsSuggestions( octokit, owner, repo, title, body ) { | ||
const suggestions = { labels: [], explanations: {} }; | ||
|
||
// Get all the Feature and Feature Group labels in the repo. | ||
const pattern = /^(\[Feature\]|\[Feature Group\])/; | ||
const repoLabels = await getAvailableLabels( octokit, owner, repo, pattern ); | ||
|
||
// If no labels are found, bail. | ||
if ( repoLabels.length === 0 ) { | ||
debug( | ||
'triage-issues > auto-label: No labels found in the repository. Aborting OpenAI request.' | ||
); | ||
return suggestions; | ||
} | ||
|
||
const prompt = `You must analyse the content below, composed of 2 data points pulled from a GitHub issue: | ||
- a title | ||
- the issue body | ||
Here is the issue title. It is the most important part of the text you must analyse: | ||
- ${ title } | ||
Here is the issue body: | ||
********************** | ||
${ body } | ||
********************** | ||
You must analyze this content, and suggest labels related to the content. | ||
The labels you will suggest must all come from the list below. | ||
Each item on the list of labels below follows the following format: - <label name>: <label description if it exists> | ||
${ repoLabels | ||
.map( label => `- ${ label.name }${ label?.description ? `: ${ label.description }` : '' }` ) | ||
.join( '\n' ) } | ||
Analyze the issue and suggest relevant labels. Rules: | ||
- Use only existing labels provided. | ||
- Include 1 '[Feature Group]' label. | ||
- Include 1 to 3 '[Feature]' labels. | ||
- Briefly explain each label choice in 1 sentence. | ||
- Format your response as a JSON object, with each suggested label as a key, and your explanation of the label choice as the value. | ||
Example response format: | ||
{ | ||
"[Feature Group] User Interaction & Engagement": "The issue involves how users interact with the platform.", | ||
"[Feature] Comments": "Specifically, it's about the commenting functionality." | ||
}`; | ||
|
||
const response = await sendOpenAiRequest( prompt, 'json_object' ); | ||
debug( `triage-issues > auto-label: OpenAI response: ${ response }` ); | ||
|
||
let parsedResponse; | ||
try { | ||
parsedResponse = JSON.parse( response ); | ||
} catch ( error ) { | ||
debug( | ||
`triage-issues > auto-label: OpenAI did not send back the expected JSON-formatted response. Error: ${ error }` | ||
); | ||
return suggestions; | ||
} | ||
|
||
const labels = Object.keys( parsedResponse ); | ||
|
||
if ( ! Array.isArray( labels ) ) { | ||
return suggestions; | ||
} | ||
|
||
return { labels, explanations: parsedResponse }; | ||
} | ||
|
||
/** | ||
* Automatically add labels to issues. | ||
* | ||
* When an issue is first opened, parse its contents, send them to OpenAI, | ||
* and add labels if any matching labels can be found. | ||
* During testing, we'll run it for any issues, not just opened, | ||
* but only on issues with the "[Experiment] Automated labeling" label. | ||
* In that situation, we'll add a label to note that the issue was processed. | ||
* | ||
* @param {WebhookPayloadIssue} payload - Issue event payload. | ||
* @param {GitHub} octokit - Initialized Octokit REST client. | ||
*/ | ||
async function aiLabeling( payload, octokit ) { | ||
const { issue, repository } = payload; | ||
const { number, body, title } = issue; | ||
const { owner, name } = repository; | ||
const ownerLogin = owner.login; | ||
|
||
const issueLabels = await getLabels( octokit, ownerLogin, name, number ); | ||
const apiKey = getInput( 'openai_api_key' ); | ||
|
||
if ( ! apiKey ) { | ||
debug( `triage-issues > auto-label: No OpenAI key is provided. Bail.` ); | ||
return; | ||
} | ||
|
||
if ( | ||
issueLabels.includes( '[Experiment] Automated labeling' ) && | ||
! issueLabels.includes( '[Experiment] AI labels added' ) | ||
) { | ||
debug( | ||
`triage-issues > auto-label: Fetching labels suggested by OpenAI for issue #${ number }` | ||
); | ||
const { labels, explanations } = await fetchOpenAiLabelsSuggestions( | ||
octokit, | ||
ownerLogin, | ||
name, | ||
title, | ||
body | ||
); | ||
|
||
if ( labels.length === 0 ) { | ||
debug( `triage-issues > auto-label: No labels suggested by OpenAI for issue #${ number }` ); | ||
} else { | ||
// Add the suggested labels to the issue. | ||
debug( | ||
`triage-issues > auto-label: Adding the following labels to issue #${ number }, as suggested by OpenAI: ${ labels.join( | ||
', ' | ||
) }` | ||
); | ||
await octokit.rest.issues.addLabels( { | ||
owner: ownerLogin, | ||
repo: name, | ||
issue_number: number, | ||
labels, | ||
} ); | ||
|
||
// During testing, post a comment on the issue with the explanations. | ||
const explanationComment = `**OpenAI suggested the following labels for this issue:** | ||
${ Object.entries( explanations ) | ||
.map( ( [ labelName, explanation ] ) => `- ${ labelName }: ${ explanation }` ) | ||
.join( '\n' ) }`; | ||
|
||
await octokit.rest.issues.createComment( { | ||
owner: ownerLogin, | ||
repo: name, | ||
issue_number: number, | ||
body: explanationComment, | ||
} ); | ||
|
||
// Add a label to note that the issue was processed. | ||
await octokit.rest.issues.addLabels( { | ||
owner: ownerLogin, | ||
repo: name, | ||
issue_number: number, | ||
labels: [ '[Experiment] AI labels added' ], | ||
} ); | ||
} | ||
} | ||
} | ||
module.exports = aiLabeling; |
File renamed without changes.
57 changes: 57 additions & 0 deletions
57
projects/github-actions/repo-gardening/src/tasks/triage-issues/get-issue-priority.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
const debug = require( '../../utils/debug' ); | ||
const getLabels = require( '../../utils/labels/get-labels' ); | ||
const findPriority = require( '../../utils/parse-content/find-priority' ); | ||
|
||
/* global GitHub, WebhookPayloadIssue */ | ||
|
||
/** | ||
* Try to figure out the priority of the issue based off its contents and existing labels. | ||
* | ||
* @param {WebhookPayloadIssue} payload - Issue event payload. | ||
* @param {GitHub} octokit - Initialized Octokit REST client. | ||
* | ||
* @return {Promise<object>} Promise resolving to an object, with 2 keys: | ||
* - labels is an array of priority Labels matching this issue, | ||
* - inferred is a boolean, returns true if the priority was inferred from the issue contents. | ||
*/ | ||
async function getIssuePriority( payload, octokit ) { | ||
const { | ||
issue: { number, body }, | ||
repository: { | ||
owner: { login: ownerLogin }, | ||
name, | ||
}, | ||
} = payload; | ||
|
||
const labels = await getLabels( octokit, ownerLogin, name, number ); | ||
const priorityLabels = labels.filter( | ||
label => label.match( /^\[Pri\].*$/ ) && label !== '[Pri] TBD' | ||
); | ||
if ( priorityLabels.length > 0 ) { | ||
debug( | ||
`triage-issues > issue priority: Issue #${ number } has the following priority labels: ${ priorityLabels.join( | ||
', ' | ||
) }` | ||
); | ||
return { | ||
labels: priorityLabels, | ||
inferred: false, | ||
}; | ||
} | ||
|
||
// If the issue does not have Priority labels yet, let's try to infer one from the issue contents. | ||
debug( | ||
`triage-issues > issue priority: Finding priority for issue #${ number } based off the issue contents.` | ||
); | ||
const priority = findPriority( body ); | ||
debug( | ||
`triage-issues > issue priority: Priority inferred from the issue contents for issue #${ number } is ${ priority }` | ||
); | ||
|
||
return { | ||
labels: [ `[Pri] ${ priority }` ], | ||
inferred: true, | ||
}; | ||
} | ||
|
||
module.exports = getIssuePriority; |
Oops, something went wrong.