Skip to content
Snippets Groups Projects
Commit 81d785b1 authored by Cédric Moreau's avatar Cédric Moreau
Browse files

automatisation de la génération du rapport

parent 9186e08a
No related branches found
No related tags found
No related merge requests found
......@@ -32,3 +32,5 @@ version-compare = "0.0.11"
tera = { version = "1", default-features = false }
weight-analyzer = {path = "../resources/weight_analyzer"}
scale-value = "0.13.0"
serde_yaml = { version = "0.9.27", default-features = false }
chrono = "0.4.31"
......@@ -57,7 +57,12 @@ enum DuniterXTaskCommand {
/// Update raw specs locally with the files published on a Release
UpdateRawSpecs { milestone: String },
/// Project management: show issues for a Milestone
ShowMilestone { milestone: String },
ShowMilestone {
milestone: String,
#[clap(long)]
/// Previous dump to compare with
compare_with: Option<String>,
},
/// Create asset in a release
CreateAssetLink {
tag: String,
......@@ -94,9 +99,10 @@ async fn main() -> Result<()> {
DuniterXTaskCommand::ReleaseRuntime { milestone, branch } => {
release_runtime::release_runtime(milestone, branch).await
}
DuniterXTaskCommand::ShowMilestone { milestone } => {
show_milestone::show_milestone(milestone).await
}
DuniterXTaskCommand::ShowMilestone {
milestone,
compare_with,
} => show_milestone::show_milestone(milestone, compare_with).await,
DuniterXTaskCommand::UpdateRawSpecs { milestone } => {
release_runtime::update_raw_specs(milestone).await
}
......
......@@ -100,13 +100,18 @@ pub(super) async fn release_runtime(milestone: String, branch: String) -> Result
// Get changes (list of MRs) from gitlab API
let changes = get_changes::get_changes(milestone.clone()).await?;
let changes_str = changes
.iter()
.map(|change| change.to_string())
.collect::<Vec<String>>()
.join("\n");
release_notes.push_str(
format!(
"
# Changes
{changes}
{changes_str}
"
)
.as_str(),
......
......@@ -25,7 +25,23 @@ use graphql_client::{GraphQLQuery, Response};
)]
pub struct GetChangesQuery;
pub(crate) async fn get_changes(milestone: String) -> Result<String> {
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct GitlabMRChange {
pub title: String,
pub mr_number: u32,
}
impl ToString for GitlabMRChange {
fn to_string(&self) -> String {
format!(
"* {mr_title} (!{mr_number})",
mr_title = self.title,
mr_number = self.mr_number
)
}
}
pub(crate) async fn get_changes(milestone: String) -> Result<Vec<GitlabMRChange>> {
// this is the important line
let request_body = GetChangesQuery::build_query(get_changes_query::Variables { milestone });
......@@ -41,13 +57,12 @@ pub(crate) async fn get_changes(milestone: String) -> Result<String> {
if let Some(project) = data.project {
if let Some(merge_requests) = project.merge_requests {
if let Some(nodes) = merge_requests.nodes {
let mut changes = String::new();
let mut changes = Vec::<GitlabMRChange>::new();
for merge_request in nodes.into_iter().flatten() {
changes.push_str(&format!(
"* {mr_title} (!{mr_number})\n",
mr_title = merge_request.title,
mr_number = merge_request.iid
));
changes.push(GitlabMRChange {
title: merge_request.title,
mr_number: merge_request.iid.parse()?,
});
}
Ok(changes)
} else {
......
......@@ -16,6 +16,7 @@
use anyhow::{anyhow, Result};
use graphql_client::{GraphQLQuery, Response};
use std::fmt::Display;
#[derive(GraphQLQuery)]
#[graphql(
......@@ -25,14 +26,26 @@ use graphql_client::{GraphQLQuery, Response};
)]
pub struct GetIssuesQuery;
pub struct Issue {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct GitlabIssue {
pub title: String,
pub number: String,
pub number: u32,
pub status: String,
pub assignees: Vec<String>,
}
pub(crate) async fn get_issues(milestone: String) -> Result<Vec<Issue>> {
impl Display for GitlabIssue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = format!(
"#{issue_number} — {issue_title}",
issue_title = self.title,
issue_number = self.number
);
write!(f, "{}", str)
}
}
pub(crate) async fn get_issues(milestone: String) -> Result<Vec<GitlabIssue>> {
// this is the important line
let request_body = GetIssuesQuery::build_query(get_issues_query::Variables { milestone });
......@@ -50,9 +63,9 @@ pub(crate) async fn get_issues(milestone: String) -> Result<Vec<Issue>> {
if let Some(nodes) = issues.nodes {
let mut changes = Vec::new();
for issue in nodes.into_iter().flatten() {
changes.push(Issue {
changes.push(GitlabIssue {
title: issue.title,
number: issue.iid.expect("iid must exist"),
number: issue.iid.expect("iid must exist").parse()?,
status: format!("{:?}", issue.state),
assignees: issue
.assignees
......
......@@ -14,57 +14,236 @@
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::release_runtime::get_changes::GitlabMRChange;
use crate::release_runtime::get_issues::GitlabIssue;
use crate::release_runtime::{get_changes, get_issues};
use anyhow::Result;
use std::fs;
pub(super) async fn show_milestone(milestone: String) -> Result<()> {
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct MilestoneDump {
milestone: String,
date: String,
changes: Vec<GitlabMRChange>,
issues: Vec<GitlabIssue>,
new_issues: IssueCounter,
unstarted_issues: IssueCounter,
taken_issues: IssueCounter,
unchanged_issues: IssueCounter,
open_issues: IssueCounter,
closed_issues: IssueCounter,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct IssueCounter {
count: u32,
issues: Vec<u32>,
}
impl IssueCounter {
fn new() -> Self {
IssueCounter {
count: 0,
issues: Vec::new(),
}
}
}
impl From<Vec<GitlabIssue>> for IssueCounter {
fn from(issues: Vec<GitlabIssue>) -> Self {
IssueCounter {
count: issues.len() as u32,
issues: issues.into_iter().map(|issue| issue.number).collect(),
}
}
}
impl MilestoneDump {
fn new() -> Self {
MilestoneDump {
milestone: "".to_string(),
date: "".to_string(),
changes: Vec::new(),
issues: Vec::new(),
new_issues: IssueCounter::new(),
taken_issues: IssueCounter::new(),
unchanged_issues: IssueCounter::new(),
unstarted_issues: IssueCounter::new(),
open_issues: IssueCounter::new(),
closed_issues: IssueCounter::new(),
}
}
}
pub(super) async fn show_milestone(milestone: String, compare_with: Option<String>) -> Result<()> {
// Get changes (list of MRs) from gitlab API
let changes = get_changes::get_changes(milestone.clone()).await?;
let mut changes = get_changes::get_changes(milestone.clone()).await?;
let mut issues = get_issues::get_issues(milestone.clone()).await?;
let previous_dump: MilestoneDump = compare_with
.map(|file| serde_yaml::from_str(&fs::read_to_string(file).unwrap()).unwrap())
.unwrap_or(MilestoneDump::new());
changes.sort_by(|a, b| b.mr_number.cmp(&a.mr_number));
let dump = MilestoneDump {
milestone: milestone.clone(),
date: chrono::Local::now().format("%Y-%m-%d").to_string(),
changes,
issues: issues.clone(),
new_issues: issues
.clone()
.into_iter()
.filter(|issue| {
!previous_dump
.issues
.iter()
.find(|p| p.number == issue.number)
.is_some()
})
.collect::<Vec<GitlabIssue>>()
.into(),
taken_issues: issues
.clone()
.into_iter()
.filter(|issue| {
previous_dump
.issues
.iter()
.find(|p| {
p.number == issue.number
&& p.status == "opened"
&& p.assignees.is_empty()
&& !issue.assignees.is_empty()
})
.is_some()
})
.collect::<Vec<GitlabIssue>>()
.into(),
unchanged_issues: issues
.clone()
.into_iter()
.filter(|issue| previous_dump.issues.iter().find(|p| *p == issue).is_some())
.collect::<Vec<GitlabIssue>>()
.into(),
unstarted_issues: issues
.clone()
.into_iter()
.filter(|issue| issue.status == "opened" && issue.assignees.is_empty())
.collect::<Vec<GitlabIssue>>()
.into(),
open_issues: issues
.clone()
.into_iter()
.filter(|issue| issue.status == "opened")
.collect::<Vec<GitlabIssue>>()
.into(),
closed_issues: issues
.clone()
.into_iter()
.filter(|issue| issue.status == "closed")
.collect::<Vec<GitlabIssue>>()
.into(),
};
let mut release_notes = format!("");
release_notes.push_str(
format!(
"
# Changes
release_notes.push_str(serde_yaml::to_string(&dump)?.as_str());
// Dump issues ordered by ID (for raw changelog — allows to compare with previous changelog to see new issues)
{changes}
"
let mut issues_str = "## Issues\n\n".to_string();
issues_str.push_str(
print_issues_section(
"\n### Ouvertes",
&mut dump
.open_issues
.issues
.iter()
.map(|i| get_issue(&issues, *i))
.collect(),
)
.as_str(),
);
// issues_str.push_str(print_issues_section("\n#### Dont non démarrées",
// &mut dump.unstarted_issues.issues.iter().map(|i| get_issue(&issues, *i)).collect()).as_str());
issues_str.push_str(
print_issues_section(
"\n#### Dont assignées depuis le dernier point",
&mut dump
.taken_issues
.issues
.iter()
.map(|i| get_issue(&issues, *i))
.filter(|i| !i.assignees.is_empty())
.collect(),
)
.as_str(),
);
issues_str.push_str(
print_issues_section(
"\n#### Dont stagnantes depuis le dernier point",
&mut dump
.unchanged_issues
.issues
.iter()
.map(|i| get_issue(&issues, *i))
.filter(|i| i.status == "opened")
.collect(),
)
.as_str(),
);
// issues_str.push_str(print_issues_section("\n### Fermées",
// &mut dump.closed_issues.issues.iter().map(|i| get_issue(&issues, *i)).collect()).as_str());
issues_str.push_str(
print_issues_section(
"\n#### Dont fermées depuis le dernier point",
&mut dump
.taken_issues
.issues
.iter()
.map(|i| get_issue(&issues, *i))
.filter(|i| i.status == "closed")
.chain(
dump.new_issues
.issues
.iter()
.map(|i| get_issue(&issues, *i))
.filter(|i| i.status == "closed"),
)
.collect(),
)
.as_str(),
);
release_notes.push_str(issues_str.as_str());
println!("{}", release_notes);
Ok(())
}
// Get changes (list of MRs) from gitlab API
let mut issues = get_issues::get_issues(milestone.clone()).await?;
fn print_issues_section(title: &str, issues: &mut Vec<GitlabIssue>) -> String {
let mut issues_str = String::new();
issues.sort_by(|a, b| b.assignees.len().cmp(&a.assignees.len()));
issues.iter().for_each(|issue| {
issues_str.push_str(&format!(
"* [#{issue_number}](https://git.duniter.org/nodes/rust/duniter-v2s/-/issues/{issue_number}) ({issue_status}",
issue_number = issue.number,
issue_status = issue.status
));
if !issue.assignees.is_empty() {
issues_str.push_str(&format!(
" — assigned to {assignees}) ",
assignees = issue.assignees.join(", ")
));
} else {
issues_str.push_str(") ");
}
issues_str.push_str(&format!(" {issue_title}\n", issue_title = issue.title));
issues_str.push_str(format!("{}\n\n", title).as_str());
issues_str.push_str(format!("Total : {}\n\n", issues.len()).as_str());
issues_str.push_str("| ID | Status | Assignees | Title |\n");
issues_str.push_str("| -- | ------ | --------- | ----- |\n");
issues.sort_by(|a, b| b.number.cmp(&a.number));
issues.iter().for_each(|issue_number| {
issues_str.push_str(print_issue(issue_number).as_str());
});
issues_str
}
release_notes.push_str(
fn print_issue(issue: &GitlabIssue) -> String {
format!(
"
# Issues
{issues_str}
"
"| [#{issue_number}](https://git.duniter.org/nodes/rust/duniter-v2s/-/issues/{issue_number}) | {issue_status} | {assignees} | {issue_title} |\n",
issue_number = issue.number,
issue_status = issue.status,
assignees = issue.assignees.join(", "),
issue_title = issue.title
)
.as_str(),
);
println!("{}", release_notes);
}
Ok(())
fn get_issue(issues: &Vec<GitlabIssue>, number: u32) -> GitlabIssue {
issues
.iter()
.find(|issue| issue.number == number)
.unwrap()
.clone()
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment