Skip to content

Commit

Permalink
Implement communities overview (#1188)
Browse files Browse the repository at this point in the history
* feat: implement communities overview, grid and list incl. search on name and short description

* chore: add communities to data generation and small SQL style fixes

---------

Co-authored-by: Ewan Cahen <[email protected]>
  • Loading branch information
dmijatovic and ewan-escience committed May 17, 2024
1 parent fedca31 commit b09d5b2
Show file tree
Hide file tree
Showing 33 changed files with 1,423 additions and 215 deletions.
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 @@ -819,8 +865,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 @@ -836,7 +882,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 @@ -848,11 +894,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 @@ -861,11 +914,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

0 comments on commit b09d5b2

Please sign in to comment.