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

Implement communities overview #1188

Merged
merged 2 commits into from
May 2, 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
74 changes: 64 additions & 10 deletions data-generation/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function generateMentions(amountExtra = 100) {
return result;
}

async function generateSoftware(amount=500) {
function generateSoftware(amount=500) {
// real software has a real concept DOI
const amountRealSoftware = Math.min(conceptDois.length, amount);
const brandNames = [];
Expand Down Expand Up @@ -317,7 +317,7 @@ function generateSoftwareHighlights(ids) {
return result;
}

async function generateProjects(amount=500) {
function generateProjects(amount=500) {
const result = [];

const projectStatuses = ['finished', 'running', 'starting'];
Expand Down Expand Up @@ -452,7 +452,7 @@ function generateUrlsForProjects(ids) {
return result;
}

async function generateOrganisations(amount=500) {
function generateOrganisations(amount=500) {
const rorIds = [
'https://ror.org/000k1q888',
'https://ror.org/006hf6230',
Expand Down Expand Up @@ -523,6 +523,25 @@ async function generateOrganisations(amount=500) {
return result;
}

function generateCommunities(amount = 50) {
const result = [];

for (let index = 0; index < amount; index++) {
const maxWords = faker.helpers.maybe(() => 5, {probability: 0.8}) ?? 31;
const name = generateUniqueCaseInsensitiveString(() => ('Community: ' + faker.word.words(faker.number.int({max: maxWords, min: 1}))).substring(0, 200));

result.push({
slug: faker.helpers.slugify(name).toLowerCase().replaceAll(/-{2,}/g, '-').replaceAll(/-+$/g, ''), // removes double dashes and trailing dashes
name: name,
short_description: faker.helpers.maybe(() => faker.lorem.paragraphs(1, '\n\n'), {probability: 0.8}) ?? null,
description: faker.helpers.maybe(() => faker.lorem.paragraphs(1, '\n\n'), {probability: 0.8}) ?? null,
logo_id: faker.helpers.maybe(() => localOrganisationLogoIds[index % localImageIds.length], {probability: 0.8}) ?? null,
});
}

return result;
}

function generateMetaPages() {
const result = [];

Expand Down Expand Up @@ -568,8 +587,8 @@ function generateNews() {
slug: 'never-dependency'
},
{
title: 'Shutting down the RSD',
slug: 'shutting-down-the-rsd'
title: 'Sunsetting the RSD',
slug: 'sunsetting-the-rsd'
},
{
title: 'The last package you will ever need',
Expand All @@ -583,6 +602,22 @@ function generateNews() {
title: 'The 5 best dependencies you never heard about',
slug: '5-best-dependencies'
},
{
title: 'Rewriting the RSD in CrabLang',
slug: 'rewrite-rsd-crablang'
},
{
title: 'The RSD joins forces with Big Company (tm)',
slug: 'rsd-joins-big-company'
},
{
title: '3 features you didn\'t know about',
slug: '3-features'
},
{
title: 'Interview with RSD founders',
slug: 'interview-rsd-founders'
},
];

const result = [];
Expand Down Expand Up @@ -646,6 +681,17 @@ function generateProjectForOrganisation(idsProjects, idsOrganisations) {
return result;
}

function generateSoftwareForCommunity(idsSoftware, idsCommunities) {
const result = generateRelationsForDifferingEntities(idsCommunities, idsSoftware, 'community', 'software');

const statuses = [{weight: 1, value: 'pending'}, {weight: 8, value: 'approved'}, {weight: 1, value: 'rejected'}];
result.forEach(entry => {
entry['status'] = faker.helpers.weightedArrayElement(statuses);
});

return result;
}

function createJWT() {
const secret = process.env.PGRST_JWT_SECRET;
return jwt.sign({ 'role': 'rsd_admin' }, secret, {expiresIn: '2m'});
Expand Down Expand Up @@ -817,8 +863,8 @@ const researchDomainsPromise = getFromBackend('/research_domain?select=id')
await Promise.all([mentionsPromise, keywordPromise, researchDomainsPromise])
.then(() => console.log('mentions, keywords, research domains done'));

let idsSoftware, idsFakeSoftware, idsRealSoftware, idsProjects, idsOrganisations;
const softwarePromise = postToBackend('/software', await generateSoftware())
let idsSoftware, idsFakeSoftware, idsRealSoftware, idsProjects, idsOrganisations, idsCommunities;
const softwarePromise = postToBackend('/software', generateSoftware())
.then(resp => resp.json())
.then(async swArray => {
idsSoftware = swArray.map(sw => sw['id']);
Expand All @@ -834,7 +880,7 @@ const softwarePromise = postToBackend('/software', await generateSoftware())
postToBackend('/software_for_software', generateSoftwareForSoftware(idsSoftware));
postToBackend('/software_highlight', generateSoftwareHighlights(idsSoftware.slice(0,10)));
});
const projectPromise = postToBackend('/project', await generateProjects())
const projectPromise = postToBackend('/project', generateProjects())
.then(resp => resp.json())
.then(async pjArray => {
idsProjects = pjArray.map(sw => sw['id']);
Expand All @@ -846,11 +892,18 @@ const projectPromise = postToBackend('/project', await generateProjects())
postToBackend('/research_domain_for_project', generateResearchDomainsForProjects(idsProjects, idsResearchDomains));
postToBackend('/project_for_project', generateSoftwareForSoftware(idsProjects));
});
const organisationPromise = postToBackend('/organisation', await generateOrganisations())
const organisationPromise = postToBackend('/organisation', generateOrganisations())
.then(resp => resp.json())
.then(async orgArray => {
idsOrganisations = orgArray.map(org => org['id']);
});

const communityPromise = postToBackend('/community', generateCommunities())
.then(resp => resp.json())
.then(async commArray => {
idsCommunities = commArray.map(comm => comm['id']);
});

await postToBackend('/meta_pages', generateMetaPages()).then(() => console.log('meta pages done'));
await postToBackend('/news?select=id', generateNews())
.then(() => getFromBackend("/news"))
Expand All @@ -859,11 +912,12 @@ await postToBackend('/news?select=id', generateNews())
.then(newsIds => postToBackend('/image_for_news', generateImagesForNews(newsIds, localImageIds)))
.then(() => console.log('news done'));

await Promise.all([softwarePromise, projectPromise, organisationPromise]).then(() => console.log('sw, pj, org done'));
await Promise.all([softwarePromise, projectPromise, organisationPromise, communityPromise]).then(() => console.log('sw, pj, org, comm done'));

await postToBackend('/software_for_project', generateRelationsForDifferingEntities(idsSoftware, idsProjects, 'software', 'project')).then(() => console.log('sw-pj done'));
await postToBackend('/software_for_organisation', generateRelationsForDifferingEntities(idsSoftware, idsOrganisations, 'software', 'organisation')).then(() => console.log('sw-org done'));
await postToBackend('/project_for_organisation', generateProjectForOrganisation(idsProjects, idsOrganisations)).then(() => console.log('pj-org done'));
await postToBackend('/software_for_community', generateSoftwareForCommunity(idsSoftware, idsCommunities)).then(() => console.log('sw-comm done'));
await postToBackend('/release', idsSoftware.map(id => ({software: id})))
.then(() => postToBackend('/release_version', generateRelationsForDifferingEntities(idsFakeSoftware, idsMentions, 'release_id', 'mention_id', 100)))
.then(() => console.log('releases done'));
Expand Down
15 changes: 8 additions & 7 deletions database/024-community.sql
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
-- SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
-- SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) <[email protected]>
-- SPDX-FileCopyrightText: 2024 Netherlands eScience Center
--
-- SPDX-License-Identifier: Apache-2.0

CREATE TABLE community (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
slug VARCHAR(200) UNIQUE NOT NULL CHECK (slug ~ '^[a-z0-9]+(-[a-z0-9]+)*$'),
slug VARCHAR(200) UNIQUE NOT NULL CHECK (slug ~ '^[a-z0-9]+(-[a-z0-9]+)*$'),
name VARCHAR(200) NOT NULL,
short_description VARCHAR(300),
description VARCHAR(10000),
primary_maintainer UUID REFERENCES account (id),
logo_id VARCHAR(40) REFERENCES image(id),
created_at TIMESTAMPTZ NOT NULL,
primary_maintainer UUID REFERENCES account (id),
logo_id VARCHAR(40) REFERENCES image(id),
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);

Expand Down Expand Up @@ -91,17 +92,17 @@ CREATE TYPE request_status AS ENUM (
);

CREATE TABLE software_for_community (
community UUID REFERENCES community (id),
software UUID REFERENCES software (id),
community UUID REFERENCES community (id),
status request_status NOT NULL DEFAULT 'pending',
PRIMARY KEY (community, software)
PRIMARY KEY (software, community)
);

CREATE FUNCTION sanitise_update_software_for_community() RETURNS TRIGGER LANGUAGE plpgsql AS
$$
BEGIN
NEW.community = OLD.community;
NEW.software = OLD.software;
NEW.community = OLD.community;
return NEW;
END
$$;
Expand Down
50 changes: 50 additions & 0 deletions database/124-community-views.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
-- SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) <[email protected]>
-- SPDX-FileCopyrightText: 2024 Netherlands eScience Center
--
-- SPDX-License-Identifier: Apache-2.0

-- Software count by community
CREATE FUNCTION software_count_by_community() RETURNS TABLE (
community UUID,
software_cnt BIGINT
) LANGUAGE sql STABLE AS
$$
SELECT
community.id,
COUNT(software_for_community.software) AS software_cnt
FROM
community
LEFT JOIN
software_for_community ON community.id = software_for_community.community
GROUP BY
community.id
;
$$;

-- rpc for community overview page
-- incl. software count
CREATE FUNCTION communities_overview() RETURNS TABLE (
id UUID,
slug VARCHAR,
name VARCHAR,
short_description VARCHAR,
logo_id VARCHAR,
software_cnt BIGINT,
created_at TIMESTAMPTZ
) LANGUAGE sql STABLE AS
$$
SELECT
community.id,
community.slug,
community."name",
community.short_description,
community.logo_id,
software_count_by_community.software_cnt,
community.created_at
FROM
community
LEFT JOIN
software_count_by_community() ON community.id = software_count_by_community.community
;
$$;
62 changes: 62 additions & 0 deletions frontend/auth/permissions/isMaintainerOfCommunity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

import {RsdRole} from '~/auth/index'
import logger from '~/utils/logger'

type isCommunityMaintainerProps = {
community: string
role?: RsdRole
account?: string
token?: string
}

export async function isCommunityMaintainer({community, role, account, token}: isCommunityMaintainerProps) {
// if no account, token, role provided
if ( typeof account == 'undefined' ||
typeof token == 'undefined' ||
typeof role == 'undefined'
) {
return false
}

// if community provided and user role rsd_admin
if (community && role === 'rsd_admin' && account) {
return true
}

const isMaintainer = await isMaintainerOfCommunity({
community,
account,
token
})

return isMaintainer
}

export async function isMaintainerOfCommunity({community, account, token}: isCommunityMaintainerProps) {
try {
if ( typeof account == 'undefined' ||
typeof token == 'undefined'
) {
// if no account, token, role provided
return false
}
console.error('isMaintainerOfCommunity...NOT IMPLEMENTED')
// const organisations = await getMaintainerOrganisations({
// token
// })
// // debugger
// if (organisations.length > 0) {
// const isMaintainer = organisations.includes(organisation)
// return isMaintainer
// }
return false
} catch (e:any) {
logger(`isMaintainerOfCommunity: ${e?.message}`, 'error')
// ERRORS AS NOT MAINTAINER
return false
}
}
28 changes: 28 additions & 0 deletions frontend/components/AppHeader/DesktopMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

import {menuItems} from '~/config/menuItems'
import Link from 'next/link'

import isActiveMenuItem from './isActiveMenuItem'

export default function DesktopMenu({activePath}:{activePath:string}) {
// console.group('DesktopMenu')
// console.log('activePath...',activePath)
// console.groupEnd()
return (
<div
className="hidden lg:flex-1 lg:flex lg:justify-center text-lg ml-4 gap-5 text-center opacity-90 font-normal ">
{menuItems.map(item => {
const isActive = isActiveMenuItem({item, activePath})
return (
<Link key={item.path} href={item.path ?? ''} className={`${isActive ? 'nav-active' : ''}`}>
{item.label}
</Link>
)
})}
</div>
)
}
Loading