Skip to content

Commit

Permalink
Add annual review check (#1101)
Browse files Browse the repository at this point in the history
Signed-off-by: Sergio Castaño Arteaga <[email protected]>
Signed-off-by: Cintia Sanchez Garcia <[email protected]>
Co-authored-by: Sergio Castaño Arteaga <[email protected]>
Co-authored-by: Cintia Sanchez Garcia <[email protected]>
  • Loading branch information
tegioz and cynthia-sg authored May 22, 2023
1 parent 4892e4e commit 255b6d7
Show file tree
Hide file tree
Showing 23 changed files with 205 additions and 21 deletions.
1 change: 1 addition & 0 deletions clomonitor-apiserver/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ mod tests {
report: Some(Report {
documentation: Documentation {
adopters: Some(CheckOutput::passed()),
annual_review: Some(CheckOutput::passed()),
code_of_conduct: Some(CheckOutput::passed()),
contributing: Some(CheckOutput::passed()),
changelog: Some(CheckOutput::passed()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
### Documentation [100%]

- [x] Adopters ([_docs_](https://clomonitor.io/docs/topics/checks/#adopters))
- [x] Annual review ([_docs_](https://clomonitor.io/docs/topics/checks/#annual-review))
- [x] Changelog ([_docs_](https://clomonitor.io/docs/topics/checks/#changelog))
- [x] Code of conduct ([_docs_](https://clomonitor.io/docs/topics/checks/#code-of-conduct))
- [x] Contributing ([_docs_](https://clomonitor.io/docs/topics/checks/#contributing))
Expand Down
1 change: 1 addition & 0 deletions clomonitor-apiserver/templates/repository-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
### Documentation [{{ value.round() }}%]

{% call check("adopters", "Adopters", report.documentation.adopters) -%}
{% call check("annual-review", "Annual review", report.documentation.annual_review) -%}
{% call check("changelog", "Changelog", report.documentation.changelog) -%}
{% call check("code-of-conduct", "Code of conduct", report.documentation.code_of_conduct) -%}
{% call check("contributing", "Contributing", report.documentation.contributing) -%}
Expand Down
76 changes: 76 additions & 0 deletions clomonitor-core/src/linter/checks/annual_review.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::linter::{
check::{CheckId, CheckInput, CheckOutput},
landscape, CheckSet,
};
use anyhow::Result;
use time::{Duration, OffsetDateTime};

/// Check identifier.
pub(crate) const ID: CheckId = "annual_review";

/// Check score weight.
pub(crate) const WEIGHT: usize = 1;

/// Check sets this check belongs to.
pub(crate) const CHECK_SETS: [CheckSet; 1] = [CheckSet::Community];

/// One year duration.
const ONE_YEAR: Duration = Duration::new(60 * 60 * 24 * 365, 0);

/// Grace period duration (60 days).
const GRACE_PERIOD: Duration = Duration::new(60 * 60 * 24 * 60, 0);

/// Check main function.
pub(crate) async fn check(input: &CheckInput<'_>) -> Result<CheckOutput> {
// Check if project information is available
let Some(project) = &input.li.project.as_ref() else {
return Ok(CheckOutput::exempt()
.exemption_reason(Some("Project information not available".to_string())));
};
let Some(project_accepted_date) = project.accepted_at.as_ref() else {
return Ok(CheckOutput::exempt()
.exemption_reason(Some("Project accepted date not available".to_string())));
};

// This check only applies to CNCF Sandbox projects
if project.foundation.foundation_id != "cncf"
|| project.maturity.as_ref().unwrap_or(&"".to_string()) != "sandbox"
{
return Ok(CheckOutput::exempt().exemption_reason(Some(
"This check only applies to CNCF Sandbox projects".to_string(),
)));
}

// Check if landscape information is available
let Some(landscape_url) = project.foundation.landscape_url.as_ref() else {
return Ok(CheckOutput::exempt()
.exemption_reason(Some("Landscape information not available".to_string())));
};
let landscape = landscape::new(landscape_url.clone()).await?;

// Check if the project has been required to present the annual review yet
let current_date = OffsetDateTime::now_utc().date();
if current_date - *project_accepted_date < ONE_YEAR + GRACE_PERIOD {
return Ok(CheckOutput::exempt().exemption_reason(Some(
"The project has not been required to present the annual review yet".to_string(),
)));
}

// Check annual review info in landscape
if let Some(last_annual_review) = landscape.get_annual_review_info(&project.name)? {
let due = last_annual_review.date + ONE_YEAR + GRACE_PERIOD;
if current_date < due {
Ok(CheckOutput::passed().url(Some(last_annual_review.url)))
} else {
Ok(CheckOutput::not_passed()
.details(Some(
"Annual review information in landscape is outdated".to_string(),
))
.url(Some(last_annual_review.url)))
}
} else {
Ok(CheckOutput::not_passed().details(Some(
"Annual review information not found in landscape".to_string(),
)))
}
}
2 changes: 2 additions & 0 deletions clomonitor-core/src/linter/checks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::collections::HashMap;

pub(crate) mod adopters;
pub(crate) mod analytics;
pub(crate) mod annual_review;
pub(crate) mod artifacthub_badge;
pub(crate) mod binary_artifacts;
pub(crate) mod changelog;
Expand Down Expand Up @@ -66,6 +67,7 @@ lazy_static! {

register_check!(adopters);
register_check!(analytics);
register_check!(annual_review);
register_check!(artifacthub_badge);
register_check!(binary_artifacts, "Binary-Artifacts");
register_check!(changelog);
Expand Down
2 changes: 1 addition & 1 deletion clomonitor-core/src/linter/checks/summary_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub(crate) async fn check(input: &CheckInput<'_>) -> Result<CheckOutput> {
}
}

// Project's summary table info in landscape
// Check project's summary table info in landscape
if let Some(landscape) = landscape {
let project_name = &input.li.project.as_ref().unwrap().name;
if let Some(summary_table) = landscape.get_summary_table_info(project_name) {
Expand Down
2 changes: 1 addition & 1 deletion clomonitor-core/src/linter/checks/website.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use anyhow::Result;
pub(crate) const ID: CheckId = "website";

/// Check score weight.
pub(crate) const WEIGHT: usize = 5;
pub(crate) const WEIGHT: usize = 4;

/// Check sets this check belongs to.
pub(crate) const CHECK_SETS: [CheckSet; 1] = [CheckSet::Community];
Expand Down
67 changes: 55 additions & 12 deletions clomonitor-core/src/linter/landscape.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anyhow::Result;
use anyhow::{Context, Result};
use cached::proc_macro::cached;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt::Display};
use time::{macros::format_description, Date};

/// Key used in the extra section of the landscape yaml file for the project
/// name in CLOMonitor.
Expand Down Expand Up @@ -40,33 +41,75 @@ pub(crate) async fn new(url: String) -> Result<Landscape> {
}

impl Landscape {
/// Return the project's annual review information if available.
pub(crate) fn get_annual_review_info(
&self,
project_name: &str,
) -> Result<Option<AnnualReview>> {
// Prepare project's annual review from available info (if any)
if let Some(project) = self.get_project(project_name) {
let annual_review = AnnualReview::from(project.extra.as_ref().unwrap())?;
return Ok(annual_review);
}

Ok(None)
}

/// Return the project's summary table information if available.
pub(crate) fn get_summary_table_info(&self, project_name: &str) -> Option<SummaryTable> {
// Locate project's entry (item) in landscape
let mut project = None;
// Prepare project's summary table from available info (if any)
if let Some(project) = self.get_project(project_name) {
let summary_table = SummaryTable::from(project.extra.as_ref().unwrap());
if summary_table != SummaryTable::default() {
return Some(summary_table);
}
}

None
}

/// Return the requested project if available.
fn get_project(&self, project_name: &str) -> Option<&Item> {
for category in &self.landscape {
for subcategory in &category.subcategories {
for item in &subcategory.items {
if let Some(extra) = &item.extra {
if let Some(clomonitor_name) = extra.get(CLOMONITOR_NAME_KEY) {
if clomonitor_name == project_name {
project = Some(item);
return Some(item);
}
}
}
}
}
}
None
}
}

// Prepare project's summary table from available info (if any)
if let Some(project) = project {
let summary_table = SummaryTable::from(project.extra.as_ref().unwrap());
if summary_table != SummaryTable::default() {
return Some(summary_table);
}
}
/// Project's annual review information.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct AnnualReview {
pub date: Date,
pub url: String,
}

None
impl AnnualReview {
fn from(extra: &HashMap<String, String>) -> Result<Option<Self>> {
let Some(date) = extra.get("annual_review_date") else {
return Ok(None);
};
let Some(url) = extra.get("annual_review_url") else {
return Ok(None);
};

let format = format_description!("[year]-[month]-[day]");
let date = Date::parse(date, &format).context("invalid annual review date in landscape")?;

Ok(Some(AnnualReview {
date,
url: url.clone(),
}))
}
}

Expand Down
8 changes: 7 additions & 1 deletion clomonitor-core/src/linter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use mockall::automock;
use postgres_types::ToSql;
use serde::{Deserialize, Serialize};
use std::{fmt, path::PathBuf, sync::Arc};
use time::Date;

mod check;
mod checks;
Expand Down Expand Up @@ -49,12 +50,15 @@ pub struct LinterInput {
#[derive(Debug, Clone, Default)]
pub struct Project {
pub name: String,
pub accepted_at: Option<Date>,
pub maturity: Option<String>,
pub foundation: Foundation,
}

/// Foundation's details
#[derive(Debug, Clone, Default)]
pub struct Foundation {
pub foundation_id: String,
pub landscape_url: Option<String>,
}

Expand Down Expand Up @@ -104,8 +108,9 @@ impl Linter for CoreLinter {
let ci = CheckInput::new(li).await?;

// Run some async checks concurrently
let (analytics, contributing, summary_table, trademark_disclaimer) = tokio::join!(
let (analytics, annual_review, contributing, summary_table, trademark_disclaimer) = tokio::join!(
run_async!(analytics, &ci),
run_async!(annual_review, &ci),
run_async!(contributing, &ci),
run_async!(summary_table, &ci),
run_async!(trademark_disclaimer, &ci),
Expand All @@ -122,6 +127,7 @@ impl Linter for CoreLinter {
let mut report = Report {
documentation: Documentation {
adopters: run!(adopters, &ci),
annual_review,
changelog: run!(changelog, &ci),
code_of_conduct: run!(code_of_conduct, &ci),
contributing,
Expand Down
2 changes: 2 additions & 0 deletions clomonitor-core/src/linter/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl Report {
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Documentation {
pub adopters: Option<CheckOutput>,
pub annual_review: Option<CheckOutput>,
pub changelog: Option<CheckOutput>,
pub code_of_conduct: Option<CheckOutput>,
pub contributing: Option<CheckOutput>,
Expand All @@ -70,6 +71,7 @@ pub struct Documentation {
section_impl!(
Documentation,
adopters,
annual_review,
changelog,
code_of_conduct,
contributing,
Expand Down
5 changes: 4 additions & 1 deletion clomonitor-core/src/score/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ mod tests {
calculate(&Report {
documentation: Documentation {
adopters: Some(CheckOutput::passed()),
annual_review: Some(CheckOutput::passed()),
code_of_conduct: Some(CheckOutput::passed()),
contributing: Some(CheckOutput::passed()),
changelog: Some(CheckOutput::passed()),
Expand Down Expand Up @@ -294,7 +295,7 @@ mod tests {
Score {
global: 99.99999999999999,
global_weight: 95,
documentation: Some(100.0),
documentation: Some(99.99999999999999),
documentation_weight: Some(30),
license: Some(100.0),
license_weight: Some(20),
Expand All @@ -314,6 +315,7 @@ mod tests {
calculate(&Report {
documentation: Documentation {
adopters: Some(CheckOutput::not_passed()),
annual_review: Some(CheckOutput::not_passed()),
code_of_conduct: Some(CheckOutput::not_passed()),
contributing: Some(CheckOutput::not_passed()),
changelog: Some(CheckOutput::not_passed()),
Expand Down Expand Up @@ -379,6 +381,7 @@ mod tests {
calculate(&Report {
documentation: Documentation {
adopters: None,
annual_review: None,
code_of_conduct: None,
contributing: Some(CheckOutput::passed()),
changelog: Some(CheckOutput::passed()),
Expand Down
5 changes: 5 additions & 0 deletions clomonitor-linter/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ pub(crate) fn display(
cell_entry("Documentation / Adopters"),
cell_check(&report.documentation.adopters),
])
.add_row(vec![
cell_entry("Documentation / Annual review"),
cell_check(&report.documentation.annual_review),
])
.add_row(vec![
cell_entry("Documentation / Changelog"),
cell_check(&report.documentation.changelog),
Expand Down Expand Up @@ -317,6 +321,7 @@ mod tests {
let report = Report {
documentation: Documentation {
adopters: Some(CheckOutput::passed()),
annual_review: Some(CheckOutput::passed()),
code_of_conduct: Some(CheckOutput::passed()),
contributing: Some(CheckOutput::passed()),
changelog: Some(CheckOutput::passed()),
Expand Down
2 changes: 2 additions & 0 deletions clomonitor-linter/src/testdata/display.golden
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Checks summary
╞═══════════════════════════════════════════════╪════════════╡
│ Documentation / Adopters ┆ ✓ │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Documentation / Annual review ┆ ✓ │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Documentation / Changelog ┆ ✓ │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Documentation / Code of conduct ┆ ✓ │
Expand Down
6 changes: 6 additions & 0 deletions clomonitor-tracker/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ impl DB for PgDB {
to_json(r.check_sets) as check_sets,
r.updated_at,
p.name as project_name,
p.accepted_at as project_accepted_at,
p.maturity::text as project_maturity,
f.foundation_id,
f.landscape_url as foundation_landscape_url
from repository r
join project p using (project_id)
Expand All @@ -73,7 +76,10 @@ impl DB for PgDB {
updated_at: row.get("updated_at"),
project: Project {
name: row.get("project_name"),
accepted_at: row.get("project_accepted_at"),
maturity: row.get("project_maturity"),
foundation: Foundation {
foundation_id: row.get("foundation_id"),
landscape_url: row.get("foundation_landscape_url"),
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ returns setof text as $$
r.url as repository_url,
r.check_sets,
(rp.data->'documentation'->'adopters'->'passed')::boolean as adopters,
(rp.data->'documentation'->'annual_review'->'passed')::boolean as annual_review,
(rp.data->'documentation'->'changelog'->'passed')::boolean as changelog,
(rp.data->'documentation'->'code_of_conduct'->'passed')::boolean as code_of_conduct,
(rp.data->'documentation'->'contributing'->'passed')::boolean as contributing,
Expand Down Expand Up @@ -48,7 +49,7 @@ returns setof text as $$
join report rp using (repository_id)
order by p.foundation_id asc, p.name asc
)
select 'Foundation,Project,Repository URL,Check Sets,Adopters,Changelog,Code of Conduct,Contributing,Governance,Maintainers,Readme,Roadmap,Summary Table,Website,License Approved,License Scanning,License SPDX ID,Analytics,ArtifactHub Badge,CLA,Community Meeting,DCO,GitHub discussions,OpenSSF best practices badge,OpenSSF Scorecard badge,Recent Release,Slack Presence,Binary Artifacts,Code Review,Dangerous Workflow,Dependency Update Tool,Maintained,SBOM,Security Policy,Signed Releases,Token Permissions,Trademark Disclaimer'
select 'Foundation,Project,Repository URL,Check Sets,Adopters,Annual Review,Changelog,Code of Conduct,Contributing,Governance,Maintainers,Readme,Roadmap,Summary Table,Website,License Approved,License Scanning,License SPDX ID,Analytics,ArtifactHub Badge,CLA,Community Meeting,DCO,GitHub discussions,OpenSSF best practices badge,OpenSSF Scorecard badge,Recent Release,Slack Presence,Binary Artifacts,Code Review,Dangerous Workflow,Dependency Update Tool,Maintained,SBOM,Security Policy,Signed Releases,Token Permissions,Trademark Disclaimer'
union all
select rtrim(ltrim(r.*::text, '('), ')') from repositories r;
$$ language sql;
1 change: 1 addition & 0 deletions database/migrations/functions/stats/get_stats.sql
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ returns json as $$
'passing_check', json_build_object(
'documentation', json_build_object(
'adopters', repositories_passing_check(p_foundation, 'documentation', 'adopters'),
'annual_review', repositories_passing_check(p_foundation, 'documentation', 'annual_review'),
'changelog', repositories_passing_check(p_foundation, 'documentation', 'changelog'),
'code_of_conduct', repositories_passing_check(p_foundation, 'documentation', 'code_of_conduct'),
'contributing', repositories_passing_check(p_foundation, 'documentation', 'contributing'),
Expand Down
Loading

0 comments on commit 255b6d7

Please sign in to comment.