diff --git a/clomonitor-apiserver/src/router.rs b/clomonitor-apiserver/src/router.rs index 148729cf..a9b3537b 100644 --- a/clomonitor-apiserver/src/router.rs +++ b/clomonitor-apiserver/src/router.rs @@ -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()), diff --git a/clomonitor-apiserver/src/testdata/repository-report.golden.md b/clomonitor-apiserver/src/testdata/repository-report.golden.md index 5e4929a2..3d67d756 100644 --- a/clomonitor-apiserver/src/testdata/repository-report.golden.md +++ b/clomonitor-apiserver/src/testdata/repository-report.golden.md @@ -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)) diff --git a/clomonitor-apiserver/templates/repository-report.md b/clomonitor-apiserver/templates/repository-report.md index 66e712df..de2f1d9a 100644 --- a/clomonitor-apiserver/templates/repository-report.md +++ b/clomonitor-apiserver/templates/repository-report.md @@ -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) -%} diff --git a/clomonitor-core/src/linter/checks/annual_review.rs b/clomonitor-core/src/linter/checks/annual_review.rs new file mode 100644 index 00000000..5687c4c2 --- /dev/null +++ b/clomonitor-core/src/linter/checks/annual_review.rs @@ -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 { + // 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(), + ))) + } +} diff --git a/clomonitor-core/src/linter/checks/mod.rs b/clomonitor-core/src/linter/checks/mod.rs index 372cc582..c46e9085 100644 --- a/clomonitor-core/src/linter/checks/mod.rs +++ b/clomonitor-core/src/linter/checks/mod.rs @@ -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; @@ -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); diff --git a/clomonitor-core/src/linter/checks/summary_table.rs b/clomonitor-core/src/linter/checks/summary_table.rs index 1810cd1c..2711b338 100644 --- a/clomonitor-core/src/linter/checks/summary_table.rs +++ b/clomonitor-core/src/linter/checks/summary_table.rs @@ -23,7 +23,7 @@ pub(crate) async fn check(input: &CheckInput<'_>) -> Result { } } - // 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) { diff --git a/clomonitor-core/src/linter/checks/website.rs b/clomonitor-core/src/linter/checks/website.rs index 35a17413..d4292df0 100644 --- a/clomonitor-core/src/linter/checks/website.rs +++ b/clomonitor-core/src/linter/checks/website.rs @@ -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]; diff --git a/clomonitor-core/src/linter/landscape.rs b/clomonitor-core/src/linter/landscape.rs index ac1379e0..6e6e39ba 100644 --- a/clomonitor-core/src/linter/landscape.rs +++ b/clomonitor-core/src/linter/landscape.rs @@ -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. @@ -40,33 +41,75 @@ pub(crate) async fn new(url: String) -> Result { } impl Landscape { + /// Return the project's annual review information if available. + pub(crate) fn get_annual_review_info( + &self, + project_name: &str, + ) -> Result> { + // 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 { - // 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) -> Result> { + 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(), + })) } } diff --git a/clomonitor-core/src/linter/mod.rs b/clomonitor-core/src/linter/mod.rs index 86f64b64..337204b7 100644 --- a/clomonitor-core/src/linter/mod.rs +++ b/clomonitor-core/src/linter/mod.rs @@ -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; @@ -49,12 +50,15 @@ pub struct LinterInput { #[derive(Debug, Clone, Default)] pub struct Project { pub name: String, + pub accepted_at: Option, + pub maturity: Option, pub foundation: Foundation, } /// Foundation's details #[derive(Debug, Clone, Default)] pub struct Foundation { + pub foundation_id: String, pub landscape_url: Option, } @@ -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), @@ -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, diff --git a/clomonitor-core/src/linter/report.rs b/clomonitor-core/src/linter/report.rs index f027b0f7..62ed6edf 100644 --- a/clomonitor-core/src/linter/report.rs +++ b/clomonitor-core/src/linter/report.rs @@ -55,6 +55,7 @@ impl Report { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Documentation { pub adopters: Option, + pub annual_review: Option, pub changelog: Option, pub code_of_conduct: Option, pub contributing: Option, @@ -70,6 +71,7 @@ pub struct Documentation { section_impl!( Documentation, adopters, + annual_review, changelog, code_of_conduct, contributing, diff --git a/clomonitor-core/src/score/mod.rs b/clomonitor-core/src/score/mod.rs index c683d4dd..9aedd524 100644 --- a/clomonitor-core/src/score/mod.rs +++ b/clomonitor-core/src/score/mod.rs @@ -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()), @@ -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), @@ -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()), @@ -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()), diff --git a/clomonitor-linter/src/table.rs b/clomonitor-linter/src/table.rs index 0a78cad3..2ac2d0a3 100644 --- a/clomonitor-linter/src/table.rs +++ b/clomonitor-linter/src/table.rs @@ -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), @@ -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()), diff --git a/clomonitor-linter/src/testdata/display.golden b/clomonitor-linter/src/testdata/display.golden index 651ad69a..4ef630a5 100644 --- a/clomonitor-linter/src/testdata/display.golden +++ b/clomonitor-linter/src/testdata/display.golden @@ -36,6 +36,8 @@ Checks summary ╞═══════════════════════════════════════════════╪════════════╡ │ Documentation / Adopters ┆ ✓ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Documentation / Annual review ┆ ✓ │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ │ Documentation / Changelog ┆ ✓ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ │ Documentation / Code of conduct ┆ ✓ │ diff --git a/clomonitor-tracker/src/db.rs b/clomonitor-tracker/src/db.rs index 8dd95091..e4af40ab 100644 --- a/clomonitor-tracker/src/db.rs +++ b/clomonitor-tracker/src/db.rs @@ -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) @@ -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"), }, }, diff --git a/database/migrations/functions/repositories/get_repositories_with_checks.sql b/database/migrations/functions/repositories/get_repositories_with_checks.sql index 5f90a6b6..1a28868a 100644 --- a/database/migrations/functions/repositories/get_repositories_with_checks.sql +++ b/database/migrations/functions/repositories/get_repositories_with_checks.sql @@ -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, @@ -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; diff --git a/database/migrations/functions/stats/get_stats.sql b/database/migrations/functions/stats/get_stats.sql index dd1b2eb4..d9235677 100644 --- a/database/migrations/functions/stats/get_stats.sql +++ b/database/migrations/functions/stats/get_stats.sql @@ -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'), diff --git a/database/tests/functions/repositories/get_repositories_with_checks.sql b/database/tests/functions/repositories/get_repositories_with_checks.sql index 03888f8d..501c9f4a 100644 --- a/database/tests/functions/repositories/get_repositories_with_checks.sql +++ b/database/tests/functions/repositories/get_repositories_with_checks.sql @@ -124,6 +124,9 @@ insert into report ( "url": "https://github.com/fluent/fluentd/blob/master/ADOPTERS.md", "passed": true }, + "annual_review": { + "passed": true + }, "changelog": { "url": "https://github.com/fluent/fluentd/blob/master/CHANGELOG.md", "passed": true @@ -221,9 +224,9 @@ select results_eq( $$, $$ values - ('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'), - ('cncf,project1,https://repo1.url,"{code,community}",t,t,t,t,t,t,t,f,f,t,t,f,Apache-2.0,GA4,f,t,f,t,t,t,t,t,f,t,t,t,f,t,f,t,f,f,f'), - ('cncf,project1,https://repo2.url,{docs},,,,,,,f,,,,t,,Apache-2.0,,,,,,,,,,,,,,,,,,,,') + ('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'), + ('cncf,project1,https://repo1.url,"{code,community}",t,t,t,t,t,t,t,t,f,f,t,t,f,Apache-2.0,GA4,f,t,f,t,t,t,t,t,f,t,t,t,f,t,f,t,f,f,f'), + ('cncf,project1,https://repo2.url,{docs},,,,,,,,f,,,,t,,Apache-2.0,,,,,,,,,,,,,,,,,,,,') $$, 'Return all repositories with all checks' ); diff --git a/database/tests/functions/stats/get_stats.sql b/database/tests/functions/stats/get_stats.sql index 07449c3b..1270c278 100644 --- a/database/tests/functions/stats/get_stats.sql +++ b/database/tests/functions/stats/get_stats.sql @@ -175,6 +175,9 @@ insert into report ( "url": "https://github.com/fluent/fluentd/blob/master/ADOPTERS.md", "passed": true }, + "annual_review": { + "passed": true + }, "changelog": { "url": "https://github.com/fluent/fluentd/blob/master/CHANGELOG.md", "passed": true @@ -310,6 +313,9 @@ insert into report ( "url": "https://github.com/fluent/fluentd/blob/master/ADOPTERS.md", "passed": true }, + "annual_review": { + "passed": true + }, "changelog": { "url": "https://github.com/fluent/fluentd/blob/master/CHANGELOG.md", "passed": true @@ -439,6 +445,9 @@ insert into report ( "adopters": { "passed": false }, + "annual_review": { + "passed": false + }, "changelog": { "passed": false }, @@ -568,6 +577,7 @@ select is( "passing_check": { "documentation": { "adopters": 67, + "annual_review": 67, "changelog": 67, "code_of_conduct": 67, "contributing": 67, diff --git a/docs/checks.md b/docs/checks.md index a40ad75c..bc890924 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -43,6 +43,7 @@ Checks are organized in `check sets`. Each `check set` defines a number of check - **community** (recommended for repositories with community content) - Documentation / Adopters + - Documentation / Annual Review - Documentation / Code of conduct - Documentation / Contributing - Documentation / Governance @@ -102,6 +103,16 @@ CASE SENSITIVE: false "(?i)\[.*adopters.*\]\(.*\)" ``` +### Annual Review + +**ID**: `annual_review` + +CNCF Sandbox projects are subject to an annual review by the TOC. This is intended to be a lightweight process to ensure that projects are on track, and getting the support they need. + +This check passes if: + +- An up to date annual review is found in the [CNCF Landscape configuration file](https://github.com/cncf/landscape/blob/master/landscape.yml) for the corresponding project. For more details please see the [annual review documentation](https://github.com/cncf/toc/blob/main/process/sandbox-annual-review.md). + ### Changelog **ID**: `changelog` diff --git a/web/src/data.tsx b/web/src/data.tsx index 938a77ba..12465d34 100644 --- a/web/src/data.tsx +++ b/web/src/data.tsx @@ -25,6 +25,7 @@ import { ImOffice } from 'react-icons/im'; import { IoIosPeople, IoMdRibbon } from 'react-icons/io'; import { MdOutlineInventory, MdPreview } from 'react-icons/md'; import { RiRoadMapLine, RiShieldStarLine } from 'react-icons/ri'; +import { SiCodereview } from 'react-icons/si'; import QualityDot from './layout/common/QualityDot'; import { @@ -216,6 +217,12 @@ export const REPORT_OPTIONS: ReportOptionInfo = { legend: List of organizations using this project in production or at stages of testing, reference: '/docs/topics/checks/#adopters', }, + [ReportOption.AnnualReview]: { + icon: , + name: 'Annual review', + legend: Sandbox projects are subject to an annual review by the TOC, + reference: '/docs/topics/checks/#annual-review', + }, [ReportOption.ApprovedLicense]: { icon: , name: 'Approved license', @@ -493,6 +500,7 @@ export const FOUNDATIONS = { export const CHECKS_PER_CATEGORY: ChecksPerCategory = { [ScoreType.Documentation]: [ ReportOption.Adopters, + ReportOption.AnnualReview, ReportOption.Changelog, ReportOption.CodeOfConduct, ReportOption.Contributing, diff --git a/web/src/layout/detail/report/OptionCell.tsx b/web/src/layout/detail/report/OptionCell.tsx index 7ab23139..72da2b93 100644 --- a/web/src/layout/detail/report/OptionCell.tsx +++ b/web/src/layout/detail/report/OptionCell.tsx @@ -111,7 +111,7 @@ const OptionCell = (props: Props) => { onClose={scrollTop} tooltipStyle > -
+
svg { width: 15px; + height: 15px; } .scoreWrapper { diff --git a/web/src/types.ts b/web/src/types.ts index bb8d215a..407549b2 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -139,6 +139,7 @@ export enum SortBy { export enum ReportOption { Adopters = 'adopters', Analytics = 'analytics', + AnnualReview = 'annual_review', ApprovedLicense = 'license_approved', ArtifactHubBadge = 'artifacthub_badge', BinaryArtifacts = 'binary_artifacts',