From 0b4c9d3f1aec48ea5c5d42aca91cf2b6d5a83721 Mon Sep 17 00:00:00 2001 From: Ewan Cahen Date: Tue, 2 Sep 2025 15:54:20 +0200 Subject: [PATCH] feat: add categories to project metadata overview --- database/106-project-views.sql | 33 +++++++++++++++++-- .../project-quality/apiProjectQuality.tsx | 20 ++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/database/106-project-views.sql b/database/106-project-views.sql index b1680d27e..b4284e54e 100644 --- a/database/106-project-views.sql +++ b/database/106-project-views.sql @@ -312,6 +312,31 @@ GROUP BY output_for_project.project; $$; + +-- only counts assigned incidental leaf nodes of the category tree +-- (nodes that can have children but of which no child is assigned) +CREATE FUNCTION count_project_categories() RETURNS TABLE ( + project UUID, + category_cnt INTEGER +) LANGUAGE sql STABLE AS +$$ +WITH project_category_paths AS ( + SELECT * + FROM + category_for_project + INNER JOIN category_path(category_for_project.category_id) ON TRUE +) +SELECT + category_for_project.project_id, + COUNT(category_for_project.category_id) +FROM + category_for_project +WHERE + NOT EXISTS (SELECT * FROM project_category_paths WHERE project_category_paths.parent = category_for_project.category_id AND project_category_paths.project_id = category_for_project.project_id) +GROUP BY category_for_project.project_id +$$; + + CREATE FUNCTION project_status() RETURNS TABLE ( project UUID, status VARCHAR(20) @@ -347,7 +372,8 @@ CREATE FUNCTION project_quality(show_all BOOLEAN DEFAULT FALSE) RETURNS TABLE ( keyword_cnt INTEGER, research_domain_cnt INTEGER, impact_cnt INTEGER, - output_cnt INTEGER + output_cnt INTEGER, + category_cnt INTEGER ) LANGUAGE sql STABLE AS $$ SELECT @@ -368,7 +394,8 @@ SELECT COALESCE(count_project_keywords.keyword_cnt, 0), COALESCE(count_project_research_domains.research_domain_cnt, 0), COALESCE(count_project_impact.impact_cnt, 0), - COALESCE(count_project_output.output_cnt, 0) + COALESCE(count_project_output.output_cnt, 0), + COALESCE(count_project_categories.category_cnt, 0) FROM project LEFT JOIN @@ -387,6 +414,8 @@ LEFT JOIN count_project_impact() ON project.id = count_project_impact.project LEFT JOIN count_project_output() ON project.id = count_project_output.project +LEFT JOIN + count_project_categories() ON project.id = count_project_categories.project WHERE CASE WHEN show_all IS TRUE THEN TRUE ELSE project.id IN (SELECT * FROM projects_of_current_maintainer()) END; $$; diff --git a/frontend/components/user/project-quality/apiProjectQuality.tsx b/frontend/components/user/project-quality/apiProjectQuality.tsx index 88ff043e4..7c5247fdc 100644 --- a/frontend/components/user/project-quality/apiProjectQuality.tsx +++ b/frontend/components/user/project-quality/apiProjectQuality.tsx @@ -1,6 +1,6 @@ +// SPDX-FileCopyrightText: 2023 - 2025 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -29,6 +29,7 @@ export type ProjectQualityProps = { research_domain_cnt: number, impact_cnt: number, output_cnt: number, + category_cnt: number, score: number } @@ -53,8 +54,9 @@ realLabels.set('keyword_cnt', {'label': 'Keywords', 'type': 'number'}) realLabels.set('research_domain_cnt', {'label': 'Research domains', 'type': 'number'}) realLabels.set('impact_cnt', {'label': 'Impact', 'type': 'number'}) realLabels.set('output_cnt', {'label': 'Output', 'type': 'number'}) +realLabels.set('category_cnt', {'label': 'Categories', 'type': 'number'}) -async function fetchProjectQuality(showAll: boolean, token:string) { +async function fetchProjectQuality(showAll: boolean, token:string): Promise { try { const url = getBaseUrl() + `/rpc/project_quality?show_all=${showAll}` const resp = await fetch(url, { @@ -64,8 +66,7 @@ async function fetchProjectQuality(showAll: boolean, token:string) { }) if (resp.status === 200) { const json:ProjectQualityProps[] = await resp.json() - const data = handleData(json) - return data + return handleData(json) } logger(`fetchProjectQuality...${resp.status}: ${resp.statusText}`) return [] @@ -75,20 +76,23 @@ async function fetchProjectQuality(showAll: boolean, token:string) { } } -function handleData(data: ProjectQualityProps[]) { +function handleData(data: ProjectQualityProps[]): ProjectQualityProps[] { data.forEach(element => { element.score = calculateScore(element) }) return data } -function calculateScore(element:ProjectQualityProps) { +function calculateScore(element:ProjectQualityProps): number { let score = 0 let kpiCount = 0 const keys = Object.keys(element) as ProjectQualityKeys[] keys.forEach((key) => { - if (key === 'title' || key === 'slug' || key === 'score') return + if (key === 'title' || key === 'slug' || key === 'score') { + return + } + const value = element[key] if (typeof value !== 'undefined' && (value === true || (Number.isInteger(value) && value as number > 0) || typeof value === 'string')){ score += 1