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

Initial implementation of relnotes handler #1835

Merged
merged 3 commits into from
Aug 25, 2024
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
31 changes: 30 additions & 1 deletion src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,34 @@ impl GithubClient {
let (body, _req_dbg) = self.send_req(req).await?;
Ok(serde_json::from_slice(&body)?)
}

pub(crate) async fn new_issue(
&self,
repo: &IssueRepository,
title: &str,
body: &str,
labels: Vec<String>,
) -> anyhow::Result<NewIssueResponse> {
#[derive(serde::Serialize)]
struct NewIssue<'a> {
title: &'a str,
body: &'a str,
labels: Vec<String>,
}
let url = format!("{}/issues", repo.url(&self));
self.json(self.post(&url).json(&NewIssue {
title,
body,
labels,
}))
.await
.context("failed to create issue")
}
}

#[derive(Debug, serde::Deserialize)]
pub struct NewIssueResponse {
pub number: u64,
}

impl User {
Expand Down Expand Up @@ -327,6 +355,7 @@ pub struct Issue {
pub head: Option<CommitBase>,
/// Whether it is open or closed.
pub state: IssueState,
pub milestone: Option<Milestone>,
}

#[derive(Debug, serde::Deserialize, Eq, PartialEq)]
Expand Down Expand Up @@ -2509,7 +2538,7 @@ impl GithubClient {
}

/// Set the milestone of an issue or PR.
async fn set_milestone(
pub async fn set_milestone(
&self,
full_repo_name: &str,
milestone: &Milestone,
Expand Down
9 changes: 9 additions & 0 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod prioritize;
pub mod project_goals;
pub mod pull_requests_assignment_update;
mod relabel;
mod relnotes;
mod review_requested;
mod review_submitted;
mod rfc_helper;
Expand Down Expand Up @@ -107,6 +108,14 @@ pub async fn handle(ctx: &Context, event: &Event) -> Vec<HandlerError> {
);
}

if let Err(e) = relnotes::handle(ctx, event).await {
log::error!(
"failed to process event {:?} with relnotes handler: {:?}",
event,
e
);
}

if let Some(config) = config
.as_ref()
.ok()
Expand Down
114 changes: 114 additions & 0 deletions src/handlers/relnotes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! This handler implements collecting release notes from issues and PRs that are tagged with
//! `relnotes`. Any such tagging will open a new issue in rust-lang/rust responsible for tracking
//! the inclusion in releases notes.
//!
//! The new issue will be closed when T-release has added the text proposed (tracked in the issue
//! description) into the final release notes PR.
//!
//! The issue description will be edited manually by teams through the GitHub UI -- in the future,
//! we might add triagebot support for maintaining that text via commands or similar.
//!
//! These issues will also be automatically milestoned when their corresponding PR or issue is. In
//! the absence of a milestone, T-release is responsible for ascertaining which release is
//! associated with the issue.

use serde::{Deserialize, Serialize};

use crate::{
db::issue_data::IssueData,
github::{Event, IssuesAction},
handlers::Context,
};

const RELNOTES_KEY: &str = "relnotes";

#[derive(Debug, Default, Deserialize, Serialize)]
struct RelnotesState {
relnotes_issue: Option<u64>,
}

pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
let Event::Issue(e) = event else {
return Ok(());
};

let repo = e.issue.repository();
if !(repo.organization == "rust-lang" && repo.repository == "rust") {
return Ok(());
}

if e.issue
.title
.starts_with("Tracking issue for release notes")
{
// Ignore these issues -- they're otherwise potentially self-recursive.
return Ok(());
}

let mut client = ctx.db.get().await;
let mut state: IssueData<'_, RelnotesState> =
IssueData::load(&mut client, &e.issue, RELNOTES_KEY).await?;

if let Some(paired) = state.data.relnotes_issue {
// Already has a paired release notes issue.

if let IssuesAction::Milestoned = &e.action {
if let Some(milestone) = &e.issue.milestone {
ctx.github
.set_milestone(&e.issue.repository().to_string(), &milestone, paired)
.await?;
}
}

return Ok(());
}

if let IssuesAction::Labeled { label } = &e.action {
if label.name == "relnotes" || label.name == "relnotes-perf" {
let title = format!(
"Tracking issue for release notes of #{}: {}",
e.issue.number, e.issue.title
);
let body = format!(
"
This issue tracks the release notes text for #{}.

- [ ] Issue is nominated for the responsible team (and T-release nomination is removed).
- [ ] Proposed text is drafted by team responsible for underlying change.
- [ ] Issue is nominated for release team review of clarity for wider audience.
- [ ] Release team includes text in release notes/blog posts.

Release notes text:

The section title will be de-duplicated by the release team with other release notes issues.
Prefer to use the standard titles from [previous releases](https://doc.rust-lang.org/nightly/releases.html).
More than one section can be included if needed.

```markdown
# Compatibility Notes
- [{}]({})
```

Release blog section (if any, leave blank if no section is expected):

```markdown
```
",
e.issue.number, e.issue.title, e.issue.html_url,
);
let resp = ctx
.github
.new_issue(
&e.issue.repository(),
&title,
&body,
vec!["relnotes".to_owned(), "I-release-nominated".to_owned()],
)
.await?;
state.data.relnotes_issue = Some(resp.number);
state.save().await?;
}
}

Ok(())
}
Loading