diff --git a/.github/workflows/e2e_tests_ubuntu.yml b/.github/workflows/e2e_tests_ubuntu.yml index fd4ad9b10..f1a4d5f85 100644 --- a/.github/workflows/e2e_tests_ubuntu.yml +++ b/.github/workflows/e2e_tests_ubuntu.yml @@ -3,30 +3,15 @@ # SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) # SPDX-FileCopyrightText: 2022 Netherlands eScience Center # SPDX-FileCopyrightText: 2022 dv4all +# SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +# SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences # # SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: EUPL-1.2 name: e2e tests ubuntu on: workflow_dispatch: - push: - branches: - - main - paths: - - "authentication/**" - - "backend-postgrest/**" - - "database/**" - - "e2e/**" - - "frontend/**" - - "nginx/**" - pull_request: - paths: - - "authentication/**" - - "backend-postgrest/**" - - "database/**" - - "e2e/**" - - "frontend/**" - - "nginx/**" jobs: ubuntu-v22: diff --git a/CITATION.cff b/CITATION.cff index 453d50073..6d1902754 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2021 - 2022 Netherlands eScience Center +# SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences # # SPDX-License-Identifier: CC0-1.0 # @@ -6,37 +7,12 @@ # Visit https://bit.ly/cffinit to generate yours today! cff-version: 1.2.0 -title: Research Software Directory (as a service) +title: Helmholtz Research Software Directory message: >- Please cite this software using the information provided in this file. type: software authors: - - given-names: Ewan Jacov - family-names: Cahen - affiliation: Netherlands eScience Center - email: e.cahen@esciencecenter.nl - - given-names: Dusan - family-names: Mijatovic - email: d.mijatovic@esciencecenter.nl - affiliation: Netherlands eScience Center - orcid: 'https://orcid.org/0000-0002-1898-4461' - - orcid: 'https://orcid.org/0000-0002-2170-3253' - affiliation: Netherlands eScience Center - given-names: Jesus - family-names: Garcia Gonzalez - email: j.g.gonzalez@esciencecenter.nl - - given-names: Jason - family-names: Maassen - email: j.maassen@esciencecenter.nl - affiliation: Netherlands eScience Center - orcid: 'https://orcid.org/0000-0002-8172-4865' - - given-names: Maaike - name-particle: de - family-names: Jong - email: m.dejong@esciencecenter.nl - affiliation: Netherlands eScience Center - orcid: 'https://orcid.org/0000-0003-4803-7411' - given-names: Christian family-names: Meeßen email: christian.meessen@gfz-potsdam.de @@ -56,24 +32,31 @@ authors: email: norman.ziegner@ufz.de affiliation: Helmholtz Centre for Environmental Research GmbH - UFZ orcid: 'https://orcid.org/0000-0001-7579-216X' -identifiers: - - type: doi - value: 10.5281/zenodo.6379973 - description: The archived releases for the sourcecode repository-code: >- - https://github.com/research-software-directory/RSD-as-a-service -url: 'https://research-software-directory.org' + https://github.com/hifis-net/RSD-as-a-service +url: 'https://helmholtz.software' abstract: >- - The Research Software Directory (as a service) is a - content management system that is tailored to - research software. Its main goal is to increase the - visibility, impact and reuse of research software. + The Helmholtz Research Software Directory is a + content management system for research software. + The goal of the Helmholtz RSD is to increase the + visibility, impact and reuse of research software + developed within the Helmholtz Association. +references: + - type: software + authors: + - name: "Netherlands eScience Center" + title: "Research Software Directory (as a service)" + doi: 10.5281/zenodo.6379973 + version: v1.16.0 + repository-code: https://github.com/research-software-directory/RSD-as-a-service keywords: - Research Software - Software Citation - FAIR Sofware - Software Impact - Software Reuse -license: Apache-2.0 -version: v1.15.0 -date-released: '2023-01-20' +license: + - EUPL-1.2 + - Apache-2.0 +version: hifis-1.5.0 +date-released: '2023-02-14' diff --git a/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json b/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json index 2c9b46dde..acd5d7934 100644 --- a/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json +++ b/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "37d981a4-d5ef-4bbd-9eb8-b46165fe8452", + "_postman_id": "cac10210-f65f-4bcd-a437-cfad2270c9b2", "name": "RSD-SaaS-auth", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -89,6 +89,130 @@ }, "response": [] }, + { + "name": "post login_for_account admin", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 201\", function () {", + "\tpm.response.to.have.status(201);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"account\": \"{{account_id}}\",\n \"provider\": \"my_provider\",\n \"sub\": \"sub\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{backend_url}}/login_for_account", + "host": [ + "{{backend_url}}" + ], + "path": [ + "login_for_account" + ] + } + }, + "response": [] + }, + { + "name": "post login_for_account 2 admin", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 201\", function () {", + "\tpm.response.to.have.status(201);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"account\": \"{{account_id_2}}\",\n \"provider\": \"my_provider\",\n \"sub\": \"sub2\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{backend_url}}/login_for_account", + "host": [ + "{{backend_url}}" + ], + "path": [ + "login_for_account" + ] + } + }, + "response": [] + }, + { + "name": "get user agreement false", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Body matches string\", function () {", + " pm.expect(pm.response.text()).to.equal(\"false\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"account_id\": \"{{account_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{backend_url}}/rpc/user_agreements_stored", + "host": [ + "{{backend_url}}" + ], + "path": [ + "rpc", + "user_agreements_stored" + ] + } + }, + "response": [] + }, { "name": "post software anonymous", "event": [ @@ -185,6 +309,215 @@ }, "response": [] }, + { + "name": "post software user not agreed ToS", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + "\tpm.response.to.have.status(400);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{jwt_user}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\t\"slug\": \"test-slug-user\",\n\t\"brand_name\": \"Test software user\",\n\t\"is_published\": true,\n\t\"short_statement\": \"Test software for testing\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{backend_url}}/software", + "host": [ + "{{backend_url}}" + ], + "path": [ + "software" + ] + } + }, + "response": [] + }, + { + "name": "patch user accept terms user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + "\tpm.response.to.have.status(200);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{jwt_user}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"agree_terms\": true,\n \"notice_privacy_statement\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{backend_url}}/account?id=eq.{{account_id}}", + "host": [ + "{{backend_url}}" + ], + "path": [ + "account" + ], + "query": [ + { + "key": "id", + "value": "eq.{{account_id}}" + } + ] + } + }, + "response": [] + }, + { + "name": "patch user accept terms user 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + "\tpm.response.to.have.status(200);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{jwt_user_2}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"agree_terms\": true,\n \"notice_privacy_statement\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{backend_url}}/account?id=eq.{{account_id_2}}", + "host": [ + "{{backend_url}}" + ], + "path": [ + "account" + ], + "query": [ + { + "key": "id", + "value": "eq.{{account_id_2}}" + } + ] + } + }, + "response": [] + }, + { + "name": "get user agreement true", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Body matches string\", function () {", + " pm.expect(pm.response.text()).to.equal(\"true\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"account_id\": \"{{account_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{backend_url}}/rpc/user_agreements_stored", + "host": [ + "{{backend_url}}" + ], + "path": [ + "rpc", + "user_agreements_stored" + ] + } + }, + "response": [] + }, { "name": "post software user", "event": [ @@ -2158,6 +2491,44 @@ }, "response": [] }, + { + "name": "delete all login_for_account admin", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + "\tpm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Check size deleted accounts\", function () {", + "\tconst jsonData = pm.response.json();", + "\tpm.expect(jsonData.length).to.be.above(0);", + "});" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{backend_url}}/login_for_account", + "host": [ + "{{backend_url}}" + ], + "path": [ + "login_for_account" + ] + } + }, + "response": [] + }, { "name": "delete all accounts admin", "event": [ @@ -2291,4 +2662,4 @@ } } ] -} +} \ No newline at end of file diff --git a/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json.license b/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json.license index 109fbdc2a..b470afa19 100644 --- a/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json.license +++ b/backend-postgrest/tests/RSD-SaaS-auth.postman_collection.json.license @@ -2,5 +2,7 @@ SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) SPDX-FileCopyrightText: 2022 Netherlands eScience Center SPDX-FileCopyrightText: 2022 dv4all +SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences SPDX-License-Identifier: CC-BY-4.0 diff --git a/data-generation/main.js b/data-generation/main.js index 94b16cb6e..4f00af84a 100644 --- a/data-generation/main.js +++ b/data-generation/main.js @@ -1,6 +1,6 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -104,11 +104,11 @@ async function generateSofware(amount=50) { '10.5281/zenodo.1051033', '10.5281/zenodo.1051039', '10.5281/zenodo.1051130', - '10.5281/zenodo.1064391', '10.5281/zenodo.1083950', '10.5281/zenodo.1145886', '10.5281/zenodo.1149010', '10.5281/zenodo.1162057', + '10.5281/zenodo.1404735', '10.5281/zenodo.1435860', '10.5281/zenodo.1436372', '10.5281/zenodo.1436464', @@ -127,7 +127,7 @@ async function generateSofware(amount=50) { '10.5281/zenodo.597984', '10.5281/zenodo.598013', '10.5281/zenodo.598204', - '10.5281/zenodo.60031', + '10.5281/zenodo.6379973', '10.5281/zenodo.6532349', '10.5281/zenodo.832894', '10.5281/zenodo.909307', @@ -491,7 +491,7 @@ function generateMetaPages() { const result = []; const titles = ['Terms of Service', 'Privacy Statement']; - const slugs = ['tos', 'privacy']; + const slugs = ['terms-of-service', 'privacy-statement']; for (let index = 0; index < titles.length; index++) { result.push({ title: titles[index], diff --git a/database/009-create-mention-table.sql b/database/009-create-mention-table.sql index 2ea6bfc32..bf34a4774 100644 --- a/database/009-create-mention-table.sql +++ b/database/009-create-mention-table.sql @@ -1,5 +1,5 @@ --- SPDX-FileCopyrightText: 2021 - 2022 Ewan Cahen (Netherlands eScience Center) --- SPDX-FileCopyrightText: 2021 - 2022 Netherlands eScience Center +-- SPDX-FileCopyrightText: 2021 - 2023 Ewan Cahen (Netherlands eScience Center) +-- SPDX-FileCopyrightText: 2021 - 2023 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -- SPDX-FileCopyrightText: 2022 dv4all -- @@ -34,10 +34,12 @@ CREATE TABLE mention ( authors VARCHAR(15000), publisher VARCHAR(255), publication_year SMALLINT, + publication_date DATE, page VARCHAR(50), image_url VARCHAR(500) CHECK (image_url ~ '^https?://'), mention_type mention_type NOT NULL, source VARCHAR(50) NOT NULL, + version VARCHAR(100), note VARCHAR(500), scraped_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL, diff --git a/database/010-create-release-table.sql b/database/010-create-release-table.sql index 57d191332..8cda2d799 100644 --- a/database/010-create-release-table.sql +++ b/database/010-create-release-table.sql @@ -1,101 +1,39 @@ --- SPDX-FileCopyrightText: 2021 - 2022 Ewan Cahen (Netherlands eScience Center) --- SPDX-FileCopyrightText: 2021 - 2022 Netherlands eScience Center +-- SPDX-FileCopyrightText: 2021 - 2023 Ewan Cahen (Netherlands eScience Center) +-- SPDX-FileCopyrightText: 2021 - 2023 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -- SPDX-FileCopyrightText: 2022 dv4all -- -- SPDX-License-Identifier: Apache-2.0 CREATE TABLE release ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - software UUID REFERENCES software (id) UNIQUE NOT NULL, - is_citable BOOLEAN, - latest_schema_dot_org VARCHAR, - releases_scraped_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL + software UUID REFERENCES software (id) PRIMARY KEY, + releases_scraped_at TIMESTAMPTZ ); -CREATE FUNCTION sanitise_insert_release() RETURNS TRIGGER LANGUAGE plpgsql AS -$$ -BEGIN - NEW.id = gen_random_uuid(); - NEW.created_at = LOCALTIMESTAMP; - NEW.updated_at = NEW.created_at; - return NEW; -END -$$; - -CREATE TRIGGER sanitise_insert_release BEFORE INSERT ON release FOR EACH ROW EXECUTE PROCEDURE sanitise_insert_release(); - - -CREATE FUNCTION sanitise_update_release() RETURNS TRIGGER LANGUAGE plpgsql AS -$$ -BEGIN - NEW.id = OLD.id; - NEW.created_at = OLD.created_at; - NEW.updated_at = LOCALTIMESTAMP; - return NEW; -END -$$; - -CREATE TRIGGER sanitise_update_release BEFORE UPDATE ON release FOR EACH ROW EXECUTE PROCEDURE sanitise_update_release(); +CREATE TABLE release_version ( + release_id UUID REFERENCES release (software), + mention_id UUID REFERENCES mention (id), + PRIMARY KEY (release_id, mention_id) +); CREATE FUNCTION software_join_release() RETURNS TABLE ( software_id UUID, slug VARCHAR, concept_doi CITEXT, - release_id UUID, + versioned_dois CITEXT[], releases_scraped_at TIMESTAMPTZ ) LANGUAGE plpgsql STABLE AS $$ BEGIN - RETURN QUERY SELECT software.id AS software_id, software.slug, software.concept_doi, release.id AS release_id, release.releases_scraped_at FROM software LEFT JOIN RELEASE ON software.id = RELEASE.software; + RETURN QUERY + SELECT software.id AS software_id, software.slug, software.concept_doi, ARRAY_AGG(mention.doi), release.releases_scraped_at + FROM software + LEFT JOIN release ON software.id = release.software + LEFT JOIN release_version ON release_version.release_id = release.software + LEFT JOIN mention ON release_version.mention_id = mention.id + GROUP BY software.id, software.slug, software.concept_doi, release.software, release.releases_scraped_at; RETURN; END $$; - - -CREATE TYPE citability AS ENUM ( - 'doi-only', - 'full' -); - -CREATE TABLE release_content ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - release_id UUID REFERENCES release (id) NOT NULL, - citability citability NOT NULL, - date_published DATE NOT NULL, - doi CITEXT NOT NULL UNIQUE CHECK (doi ~ '^10(\.\w+)+/\S+$' AND LENGTH(doi) <= 255), - tag VARCHAR NOT NULL, - url VARCHAR NOT NULL, - bibtex VARCHAR, - cff VARCHAR, - codemeta VARCHAR, - endnote VARCHAR, - ris VARCHAR, - schema_dot_org VARCHAR -); - - -CREATE FUNCTION sanitise_insert_release_content() RETURNS TRIGGER LANGUAGE plpgsql AS -$$ -BEGIN - NEW.id = gen_random_uuid(); - return NEW; -END -$$; - -CREATE TRIGGER sanitise_insert_release_content BEFORE INSERT ON release_content FOR EACH ROW EXECUTE PROCEDURE sanitise_insert_release_content(); - - -CREATE FUNCTION sanitise_update_release_content() RETURNS TRIGGER LANGUAGE plpgsql AS -$$ -BEGIN - NEW.id = OLD.id; - return NEW; -END -$$; - -CREATE TRIGGER sanitise_update_release_content BEFORE UPDATE ON release_content FOR EACH ROW EXECUTE PROCEDURE sanitise_update_release_content(); diff --git a/database/011-create-account-table.sql b/database/011-create-account-table.sql index 06a515407..490e1fbdd 100644 --- a/database/011-create-account-table.sql +++ b/database/011-create-account-table.sql @@ -2,13 +2,19 @@ -- SPDX-FileCopyrightText: 2021 - 2022 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -- SPDX-FileCopyrightText: 2022 dv4all +-- SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +-- SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -- -- SPDX-License-Identifier: Apache-2.0 CREATE TABLE account ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL + updated_at TIMESTAMPTZ NOT NULL, + agree_terms BOOLEAN DEFAULT FALSE NOT NULL, + agree_terms_updated_at TIMESTAMPTZ NOT NULL, + notice_privacy_statement BOOLEAN DEFAULT FALSE NOT NULL, + notice_privacy_statement_updated_at TIMESTAMPTZ NOT NULL ); CREATE FUNCTION sanitise_insert_account() RETURNS TRIGGER LANGUAGE plpgsql AS @@ -17,6 +23,8 @@ BEGIN NEW.id = gen_random_uuid(); NEW.created_at = LOCALTIMESTAMP; NEW.updated_at = NEW.created_at; + NEW.agree_terms_updated_at = NEW.created_at; + NEW.notice_privacy_statement_updated_at = NEW.created_at; return NEW; END $$; @@ -30,6 +38,12 @@ BEGIN NEW.id = OLD.id; NEW.created_at = OLD.created_at; NEW.updated_at = LOCALTIMESTAMP; + IF NEW.agree_terms != OLD.agree_terms THEN + NEW.agree_terms_updated_at = NEW.updated_at; + END IF; + IF NEW.notice_privacy_statement != OLD.notice_privacy_statement THEN + NEW.notice_privacy_statement_updated_at = NEW.updated_at; + END IF; return NEW; END $$; diff --git a/database/012-create-organisation-table.sql b/database/012-create-organisation-table.sql index 48b14e852..d68a0ccdf 100644 --- a/database/012-create-organisation-table.sql +++ b/database/012-create-organisation-table.sql @@ -1,7 +1,7 @@ +-- SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +-- SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -- SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) --- SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) --- SPDX-FileCopyrightText: 2022 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 dv4all -- -- SPDX-License-Identifier: Apache-2.0 @@ -96,21 +96,23 @@ $$; CREATE TRIGGER sanitise_update_organisation BEFORE UPDATE ON organisation FOR EACH ROW EXECUTE PROCEDURE sanitise_update_organisation(); -- including the parent itself -CREATE OR REPLACE FUNCTION list_child_organisations(parent_id UUID) RETURNS TABLE (organisation_id UUID) STABLE LANGUAGE plpgsql AS +CREATE FUNCTION list_child_organisations(parent_id UUID) RETURNS TABLE (organisation_id UUID, organisation_name VARCHAR) STABLE LANGUAGE plpgsql AS $$ DECLARE child_organisations UUID[]; -DECLARE search_child_organisations UUID[]; +DECLARE child_names VARCHAR[]; +DECLARE search_child_organisations UUID[]; -- used as a stack DECLARE current_organisation UUID; BEGIN --- breadth-first search to find all child organisations +-- depth-first search to find all child organisations search_child_organisations = search_child_organisations || parent_id; WHILE CARDINALITY(search_child_organisations) > 0 LOOP current_organisation = search_child_organisations[CARDINALITY(search_child_organisations)]; child_organisations = child_organisations || current_organisation; + child_names = child_names || (SELECT name FROM organisation WHERE id = current_organisation); search_child_organisations = trim_array(search_child_organisations, 1); search_child_organisations = search_child_organisations || (SELECT ARRAY(SELECT organisation.id FROM organisation WHERE parent = current_organisation)); END LOOP; - RETURN QUERY SELECT UNNEST(child_organisations); + RETURN QUERY SELECT * FROM UNNEST(child_organisations, child_names); END $$; @@ -147,7 +149,8 @@ $$; CREATE FUNCTION organisation_route( IN id UUID, OUT organisation UUID, - OUT rsd_path VARCHAR + OUT rsd_path VARCHAR, + OUT parent_names VARCHAR ) STABLE LANGUAGE plpgsql AS $$ @@ -155,20 +158,24 @@ DECLARE current_org UUID := id; route VARCHAR := ''; slug VARCHAR; + names VARCHAR := ''; + current_name VARCHAR; BEGIN WHILE current_org IS NOT NULL LOOP SELECT organisation.slug, - organisation.parent + organisation.parent, + organisation.name FROM organisation WHERE organisation.id = current_org - INTO slug, current_org; + INTO slug, current_org, current_name; -- combine paths in reverse order - route := CONCAT(slug,'/',route); + route := CONCAT(slug, '/', route); + names := CONCAT(current_name, ' -> ', names); END LOOP; - SELECT id, route INTO organisation,rsd_path; + SELECT id, route, LEFT(names, -4) INTO organisation, rsd_path, parent_names; RETURN; END $$; diff --git a/database/020-row-level-security.sql b/database/020-row-level-security.sql index b81169ec7..59cecf176 100644 --- a/database/020-row-level-security.sql +++ b/database/020-row-level-security.sql @@ -1,5 +1,5 @@ --- SPDX-FileCopyrightText: 2021 - 2022 Ewan Cahen (Netherlands eScience Center) --- SPDX-FileCopyrightText: 2021 - 2022 Netherlands eScience Center +-- SPDX-FileCopyrightText: 2021 - 2023 Ewan Cahen (Netherlands eScience Center) +-- SPDX-FileCopyrightText: 2021 - 2023 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -- SPDX-FileCopyrightText: 2022 dv4all -- @@ -383,7 +383,10 @@ CREATE POLICY admin_all_rights ON research_domain_for_project TO rsd_admin ALTER TABLE mention ENABLE ROW LEVEL SECURITY; CREATE POLICY anyone_can_read ON mention FOR SELECT TO rsd_web_anon, rsd_user - USING (id IN (SELECT mention FROM mention_for_software) OR id IN (SELECT mention FROM output_for_project) OR id IN (SELECT mention FROM impact_for_project)); + USING (id IN (SELECT mention FROM mention_for_software) + OR id IN (SELECT mention FROM output_for_project) + OR id IN (SELECT mention FROM impact_for_project) + OR id IN (SELECT mention_id FROM release_version)); CREATE POLICY maintainer_can_read ON mention FOR SELECT TO rsd_user USING (TRUE); @@ -455,15 +458,15 @@ CREATE POLICY admin_all_rights ON release TO rsd_admin WITH CHECK (TRUE); -ALTER TABLE release_content ENABLE ROW LEVEL SECURITY; +ALTER TABLE release_version ENABLE ROW LEVEL SECURITY; -CREATE POLICY anyone_can_read ON release_content FOR SELECT TO rsd_web_anon, rsd_user - USING (release_id IN (SELECT id FROM release)); +CREATE POLICY anyone_can_read ON release_version FOR SELECT TO rsd_web_anon, rsd_user + USING (release_id IN (SELECT software FROM release)); -CREATE POLICY maintainer_select ON release_content FOR SELECT TO rsd_user - USING (release_id IN (SELECT id FROM release)); +CREATE POLICY maintainer_select ON release_version FOR SELECT TO rsd_user + USING (release_id IN (SELECT software FROM release)); -CREATE POLICY admin_all_rights ON release_content TO rsd_admin +CREATE POLICY admin_all_rights ON release_version TO rsd_admin USING (TRUE) WITH CHECK (TRUE); diff --git a/database/021-create-global-triggers.sql b/database/021-create-global-triggers.sql new file mode 100644 index 000000000..94ae59999 --- /dev/null +++ b/database/021-create-global-triggers.sql @@ -0,0 +1,116 @@ +-- SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +-- SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +-- SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +-- SPDX-FileCopyrightText: 2023 Netherlands eScience Center +-- +-- SPDX-License-Identifier: Apache-2.0 + +-- Checks whether the user agreed to the RSD terms and pricacy statement +CREATE FUNCTION check_user_agreement_on_action() RETURNS TRIGGER LANGUAGE plpgsql AS +$$ +BEGIN + IF + CURRENT_USER <> 'rsd_admin' AND NOT + (SELECT rolsuper FROM pg_roles WHERE rolname = CURRENT_USER) AND + (SELECT * FROM user_agreements_stored(uuid(current_setting('request.jwt.claims', FALSE)::json->>'account'))) = FALSE + THEN + RAISE EXCEPTION USING MESSAGE = 'You need to agree to our Terms of Service and the Privacy Statement before proceeding. Please open your user profile settings to agree.'; + ELSE + RETURN NEW; + END IF; +END +$$; + +-- Triggers for UPDATE +CREATE PROCEDURE check_user_agreement_on_update_all_tables() LANGUAGE plpgsql AS +$$ +DECLARE + _sql VARCHAR; +BEGIN + FOR _sql IN SELECT CONCAT ( + 'CREATE TRIGGER check_', + quote_ident(table_name), + '_before_update BEFORE UPDATE ON ', + quote_ident(table_name), + ' FOR EACH STATEMENT EXECUTE FUNCTION check_user_agreement_on_action();' + ) + FROM + information_schema.tables + WHERE + table_schema = 'public' AND + table_name NOT IN ('account', 'login_for_account') + LOOP + EXECUTE _sql; + END LOOP; +END +$$; + +CALL check_user_agreement_on_update_all_tables(); + +-- Triggers for INSERT +CREATE PROCEDURE check_user_agreement_on_insert_all_tables() LANGUAGE plpgsql AS +$$ +DECLARE + _sql VARCHAR; +BEGIN + FOR _sql IN SELECT CONCAT ( + 'CREATE TRIGGER check_', + quote_ident(table_name), + '_before_insert BEFORE INSERT ON ', + quote_ident(table_name), + ' FOR EACH STATEMENT EXECUTE FUNCTION check_user_agreement_on_action();' + ) + FROM + information_schema.tables + WHERE + table_schema = 'public' AND + table_name NOT IN ('account', 'login_for_account') + LOOP + EXECUTE _sql; + END LOOP; +END +$$; + +CALL check_user_agreement_on_insert_all_tables(); + +-- Checks whether the user agreed to the RSD terms and pricacy statement +CREATE FUNCTION check_user_agreement_on_delete_action() RETURNS TRIGGER LANGUAGE plpgsql AS +$$ +BEGIN + IF + CURRENT_USER <> 'rsd_admin' AND NOT + (SELECT rolsuper FROM pg_roles WHERE rolname = CURRENT_USER) AND + (SELECT * FROM user_agreements_stored(uuid(current_setting('request.jwt.claims', FALSE)::json->>'account'))) = FALSE + THEN + RAISE EXCEPTION USING MESSAGE = 'You need to agree to our Terms of Service and the Privacy Statement before proceeding. Please open your user profile settings to agree.'; + ELSE + RETURN OLD; + END IF; +END +$$; + +-- Triggers for DELETE +CREATE PROCEDURE check_user_agreement_on_delete_all_tables() LANGUAGE plpgsql AS +$$ +DECLARE + _sql VARCHAR; +BEGIN + FOR _sql IN SELECT CONCAT ( + 'CREATE TRIGGER check_', + quote_ident(table_name), + '_before_delete BEFORE DELETE ON ', + quote_ident(table_name), + ' FOR EACH STATEMENT EXECUTE FUNCTION check_user_agreement_on_delete_action();' + ) + FROM + information_schema.tables + WHERE + table_schema = 'public' AND + table_name NOT IN ('account', 'login_for_account') + LOOP + EXECUTE _sql; + END LOOP; +END +$$; + +CALL check_user_agreement_on_delete_all_tables(); diff --git a/database/100-create-api-views.sql b/database/100-create-api-views.sql index a5c62b30c..0701875f5 100644 --- a/database/100-create-api-views.sql +++ b/database/100-create-api-views.sql @@ -1,8 +1,10 @@ --- SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) +-- SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) -- SPDX-FileCopyrightText: 2021 - 2023 dv4all -- SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) (dv4all) --- SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) --- SPDX-FileCopyrightText: 2022 Netherlands eScience Center +-- SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +-- SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center +-- SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +-- SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -- -- SPDX-License-Identifier: Apache-2.0 @@ -437,6 +439,30 @@ BEGIN END $$; +-- Software releases count by organisation +CREATE FUNCTION release_cnt_by_organisation() RETURNS TABLE ( + id UUID, + slug VARCHAR, + release_cnt BIGINT +) LANGUAGE plpgsql STABLE AS +$$ +BEGIN RETURN QUERY + SELECT + organisations_of_software.id, + organisations_of_software.slug, + COUNT(*) AS release_cnt + FROM + "release" + INNER JOIN + release_version ON release_version.release_id="release".software + INNER JOIN + organisations_of_software("release".software) ON "release".software = organisations_of_software.software + GROUP BY + organisations_of_software.id, organisations_of_software.slug + ; +END +$$; + -- Organisations overview -- we pass public param to count functions to get public/private count -- public count is default, @@ -451,10 +477,12 @@ CREATE FUNCTION organisations_overview(public BOOLEAN DEFAULT TRUE) RETURNS TABL website VARCHAR, is_tenant BOOLEAN, rsd_path VARCHAR, + parent_names VARCHAR, logo_id VARCHAR, software_cnt BIGINT, project_cnt BIGINT, children_cnt BIGINT, + release_cnt BIGINT, score BIGINT ) LANGUAGE plpgsql STABLE AS $$ @@ -470,10 +498,12 @@ BEGIN organisation.website, organisation.is_tenant, organisation_route.rsd_path, + organisation_route.parent_names, organisation.logo_id, software_count_by_organisation.software_cnt, project_count_by_organisation.project_cnt, children_count_by_organisation.children_cnt, + release_cnt_by_organisation.release_cnt, ( COALESCE(software_count_by_organisation.software_cnt,0) + COALESCE(project_count_by_organisation.project_cnt,0) @@ -488,6 +518,8 @@ BEGIN project_count_by_organisation(public) ON project_count_by_organisation.organisation = organisation.id LEFT JOIN children_count_by_organisation() ON children_count_by_organisation.parent = organisation.id + LEFT JOIN + release_cnt_by_organisation() ON release_cnt_by_organisation.id = organisation.id ; END $$; @@ -1477,3 +1509,63 @@ BEGIN RETURN QUERY display_name ASC; END $$; + +-- Software releases +-- release info is scraped from Zenodo +-- one software belongs to multiple organisations +CREATE FUNCTION software_release() RETURNS TABLE ( + software_id UUID, + software_slug VARCHAR, + software_name VARCHAR, + release_doi CITEXT, + release_tag VARCHAR, + release_date DATE, + release_year SMALLINT, + release_authors VARCHAR, + organisation_slug VARCHAR[] +) LANGUAGE plpgsql STABLE AS +$$ +BEGIN RETURN QUERY + SELECT + software.id AS software_id, + software.slug AS software_slug, + software.brand_name AS software_name, + mention.doi AS release_doi, + mention.version AS release_tag, + mention.publication_date AS release_date, + mention.publication_year AS release_year, + mention.authors AS release_authors, + ARRAY_AGG(organisations_of_software.slug) AS organisation_slug + FROM + release_version + INNER JOIN + "release" ON "release".software = release_version.release_id + INNER JOIN + software ON software.id = "release".software + INNER JOIN + mention ON mention.id = release_version.mention_id + LEFT JOIN + organisations_of_software(software.id) ON software.id = organisations_of_software.software + GROUP BY + software_id, release_doi, release_tag, release_date, release_year, release_authors + ; +END +$$; + + +-- Check whether user agreed on Terms of Service and read the Privacy Statement +CREATE FUNCTION user_agreements_stored(account_id UUID) RETURNS BOOLEAN LANGUAGE plpgsql STABLE AS +$$ +BEGIN + RETURN ( + SELECT ( + account.agree_terms = TRUE AND + account.notice_privacy_statement = TRUE + ) + FROM + account + WHERE + account.id = account_id + ); +END +$$; diff --git a/deployment/helmholtz/data/settings.json b/deployment/helmholtz/data/settings.json index 44131db48..2af9eb73e 100644 --- a/deployment/helmholtz/data/settings.json +++ b/deployment/helmholtz/data/settings.json @@ -2,7 +2,9 @@ "host": { "name": "helmholtz", "email": "support@hifis.net", - "logo_url": "/images/LogoHIFISWhite.svg" + "logo_url": "/images/LogoHIFISWhite.svg", + "terms_of_service_url": "/page/terms-of-service/", + "privacy_statement_url": "/page/privacy-statement/" }, "links": [ { @@ -19,11 +21,6 @@ "label": "Technical Documentation", "url": "https://research-software-directory.github.io/RSD-as-a-service", "target": "_blank" - }, - { - "label": "Data protection declaration", - "url": "https://www.gfz-potsdam.de/en/data-protection/", - "target": "_blank" } ], "theme": { diff --git a/documentation/docs/api.md b/documentation/docs/api.md index 5093e7ea1..912c26149 100644 --- a/documentation/docs/api.md +++ b/documentation/docs/api.md @@ -7,7 +7,9 @@ SPDX-License-Identifier: CC-BY-4.0 # API -We use PostgREST for the API, see the [documentation](https://postgrest.org/en/v9.0/api.html) on how to use the API. +We use PostgREST for the API, see the [documentation](https://postgrest.org/en/v10.0/api.html) on how to use the API. + +You can also visit the [Swagger UI here](https://research-software-directory.org/swagger/) The database diagram for [release 1.1.0](https://github.com/research-software-directory/RSD-as-a-service/releases/tag/v1.1.0) is as follows: diff --git a/e2e/Dockerfile b/e2e/Dockerfile index 7728ae0db..1c570b720 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -1,11 +1,11 @@ -# SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +# SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +# SPDX-FileCopyrightText: 2022 - 2023 dv4all # SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -# SPDX-FileCopyrightText: 2022 dv4all # # SPDX-License-Identifier: Apache-2.0 # ensure same version is used in package.json for local testing -FROM mcr.microsoft.com/playwright:v1.28.1-focal +FROM mcr.microsoft.com/playwright:v1.30.0-focal # create working dir WORKDIR /app diff --git a/e2e/helpers/globalSetup.ts b/e2e/helpers/globalSetup.ts index f9301a2a0..9f956dc8e 100644 --- a/e2e/helpers/globalSetup.ts +++ b/e2e/helpers/globalSetup.ts @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -32,7 +32,7 @@ async function globalSetup(config: FullConfig) { // extract from configuration (there is always one project in array) // global use is stored in the root but it cannot be accessed directly? const {baseURL, storageState} = config.projects[0].use - console.log('globalSetup...timeout...', config.projects[0].timeout) + // console.log('globalSetup...timeout...', config.projects[0].timeout) // launch chromium browser // set headles = false if you want to see/debug const browser = await chromium.launch({ diff --git a/e2e/helpers/project.ts b/e2e/helpers/project.ts index 60849dc95..49da6ba01 100644 --- a/e2e/helpers/project.ts +++ b/e2e/helpers/project.ts @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -7,6 +7,7 @@ import {expect, Page} from '@playwright/test' import {Person} from '../mocks/mockPerson' import {MockedProject} from '../mocks/mockProject' import {CreateSoftwareProps} from '../mocks/mockSoftware' +import {acceptUserAgreement} from './userAgreement' import {fillAutosaveInput, generateId, uploadFile} from './utils' export async function createProject({title, desc, slug, page}: CreateSoftwareProps) { @@ -23,10 +24,15 @@ export async function createProject({title, desc, slug, page}: CreateSoftwarePro await addMenu.click() // click new project await Promise.all([ - page.waitForNavigation(), + page.waitForURL('**/projects/add', { + waitUntil: 'networkidle' + }), newProject.click() ]) + // accept user agreement if modal present + await acceptUserAgreement(page) + // fill in the form await Promise.all([ // fill in title @@ -95,22 +101,31 @@ export async function editProjectInput(page: Page, mockProject: MockedProject) { } export async function addFundingOrganisation(page: Page, organisation: string) { - const fundingInput = page.getByLabel('Find funding organisation') + const fundingInput = page.getByRole('combobox', {name: 'Find funding organisation'}) + // await page.pause() await Promise.all([ - fundingInput.fill(organisation), - // wait untill options list is shown - page.waitForSelector('#async-autocomplete-listbox') + page.waitForResponse(/api.ror.org\/organizations/), + page.waitForLoadState('networkidle'), + fundingInput.fill(organisation) ]) - // select organisation option - const option = page.getByRole('option', { - name: organisation - }) + + // await page.pause() + // get list + const listbox = page.locator('#async-autocomplete-listbox') + // select all options + const options = listbox.getByRole('option') + const option = await options + .filter({ + hasText: RegExp(organisation,'i') + }) + .first() + await Promise.all([ + // select first option + option.click(), // wait for api update - page.waitForRequest(/\/project_for_organisation/), - // select first matched item - option.first().click(), + page.waitForRequest(/\/project_for_organisation/) ]) } @@ -268,7 +283,8 @@ export async function openEditTeamPage(page: Page) { } export async function createTeamMember(page, contact: Person) { - const findContributor = page.getByLabel('Find or add team member') + // const findContributor = page.getByLabel('Find or add team member') + const findContributor = page.getByRole('combobox', {name: 'Find or add team member'}) // search for contact await Promise.all([ page.waitForResponse(RegExp(contact.apiUrl)), @@ -318,7 +334,9 @@ export async function createTeamMember(page, contact: Person) { } export async function importTeamMemberByOrcid(page: Page, contact: Person) { - const findContributor = page.getByLabel('Find or add team member') + // const findContributor = page.getByLabel('Find or add team member') + const findContributor = page.getByRole('combobox', {name: 'Find or add team member'}) + // search for contact await Promise.all([ page.waitForResponse(RegExp(contact.apiUrl)), diff --git a/e2e/helpers/software.ts b/e2e/helpers/software.ts index 33dc8e28b..f05a00cdb 100644 --- a/e2e/helpers/software.ts +++ b/e2e/helpers/software.ts @@ -1,6 +1,6 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -8,9 +8,13 @@ import {expect, Page} from '@playwright/test' import {Person} from '../mocks/mockPerson' import {CreateSoftwareProps, MockedSoftware} from '../mocks/mockSoftware' import {Testimonial} from '../mocks/mockTestimonials' +import {acceptUserAgreement} from './userAgreement' import {fillAutosaveInput, generateId, uploadFile} from './utils' export async function createSoftware({title, desc, slug, page}: CreateSoftwareProps) { + // accept user agreement first + // await acceptUserAgreementInSettings(page) + // get add menu item const addMenu = page.getByTestId('add-menu-button') const newSoftware = page.getByRole('menuitem', { @@ -23,9 +27,15 @@ export async function createSoftware({title, desc, slug, page}: CreateSoftwarePr await addMenu.click() // open add software page await Promise.all([ - page.waitForNavigation(), + page.waitForURL('**/software/add',{ + waitUntil: 'networkidle' + }), newSoftware.click() ]) + + // accept user agreement if modal present + await acceptUserAgreement(page) + // add name await Promise.all([ // fill in software name @@ -40,8 +50,7 @@ export async function createSoftware({title, desc, slug, page}: CreateSoftwarePr const url = RegExp(`${inputSlug}/edit`) // click save button await Promise.all([ - page.waitForNavigation({ - url, + page.waitForURL(url,{ waitUntil: 'networkidle' }), saveBtn.click() @@ -254,7 +263,8 @@ export async function editFirstContact(page) { } export async function createContact(page, contact: Person) { - const findContributor = page.getByLabel('Find or add contributor') + // find contributor input + const findContributor = page.getByRole('combobox', {name: 'Find or add contributor'}) // search for contact await Promise.all([ page.waitForResponse(RegExp(contact.apiUrl)), diff --git a/e2e/helpers/userAgreement.ts b/e2e/helpers/userAgreement.ts new file mode 100644 index 000000000..5ebc9b42b --- /dev/null +++ b/e2e/helpers/userAgreement.ts @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {expect, Page} from '@playwright/test' + +// ONLY after login +export async function acceptUserAgreementInSettings(page: Page) { + // open user menu + await page.getByTestId('user-menu-button').click() + // click My settings menu option + const mySettings = page.getByRole('menuitem', {name: 'My settings'}) + + // open user/settings page + await Promise.all([ + page.waitForURL('**/user/settings'), + mySettings.click() + ]) + + // await page.pause() + // find reference to form + const uaForm = page.locator('#profile-settings-form') + // find checkboxes + const checkboxes = await uaForm.getByTestId('controlled-switch-label').all() + expect(checkboxes.length).toBeGreaterThan(0) + // const checkboxes = await page.getByTestId('controlled-switch-label').all() + // expect(checkboxes.length).toBeGreaterThan(0) + + // check/accept all options + for (const checkbox of checkboxes) { + await checkbox.check() + } +} + +export async function acceptUserAgreement(page: Page) { + // wait for user agreement api call + const uaModal = page.getByTestId('user-agreement-modal',) + + // check if user agreement modal is shown + if (await uaModal.isVisible() === false) { + // await page.pause() + return true + } + + // find checkboxes + const checkboxes = await uaModal.getByTestId('controlled-switch-label').all() + + if (checkboxes.length > 0) { + await page.pause() + + // check/accept all options + for (const checkbox of checkboxes) { + if (await checkbox.isChecked() === false) { + await Promise.all([ + checkbox.check(), + page.waitForRequest(req => req.method() === 'PATCH') + ]) + } + } + + // get OK button + const okBtn = uaModal.getByRole('button', {name: 'OK'}) + // we need to wait a bit for state to change + okBtn.waitFor({state:'visible'}) + expect(await okBtn.isEnabled()).toBe(true) + + // click OK button + okBtn.click() + } +} diff --git a/e2e/helpers/utils.ts b/e2e/helpers/utils.ts index fcb2a855d..fdc5c5654 100644 --- a/e2e/helpers/utils.ts +++ b/e2e/helpers/utils.ts @@ -1,6 +1,6 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -113,7 +113,9 @@ export async function openEditSection(page:Page,name:string) { } export async function addOrganisation(page, organisation: Organisation, apiUrl) { - const findOrganisation = page.getByLabel('Find or add organisation') + + // const findOrganisation = page.getByLabel('Find or add organisation') + const findOrganisation = page.getByRole('combobox', {name: 'Find or add organisation'}) // check if no organisation message is present const alert = await page.getByRole('alert') @@ -154,7 +156,7 @@ export async function addOrganisation(page, organisation: Organisation, apiUrl) const options = page.getByTestId('find-organisation-option') const option = await options .filter({ - hasText:RegExp(organisation.name) + hasText:RegExp(organisation.name,'i') }) .first() const source = await option.getByTestId('organisation-list-item-source').textContent() @@ -222,19 +224,26 @@ export async function addCitation(page, input:string, waitForResponse:string) { findMention.fill(input), ]) - // select first options - const option = page.getByRole('option', { - name: input - }) + // get list + const listbox = page.locator('#async-autocomplete-listbox') + // select all options + const options = listbox.getByRole('option') + const option = await options + .filter({ + hasText: RegExp(input,'i') + }) + .first() + await Promise.all([ page.waitForResponse(RegExp(waitForResponse)), - option.first().click(), + option.click(), ]) // validate - const mentions = page.getByTestId('mention-item-base').filter({ - name: input - }) + const mentions = page.getByTestId('mention-item-base') + .filter({ + hasText: RegExp(input,'i') + }) // we should have at least one item expect(await mentions.count()).toBeGreaterThan(0) diff --git a/e2e/mocks/mockCitations.ts b/e2e/mocks/mockCitations.ts index fdf8d8a62..b3f44e633 100644 --- a/e2e/mocks/mockCitations.ts +++ b/e2e/mocks/mockCitations.ts @@ -1,5 +1,6 @@ +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // // SPDX-License-Identifier: Apache-2.0 @@ -15,8 +16,8 @@ export const mockCitations = { '10.1016/j.future.2018.08.004' ], output: [ - '10.1017/S0033291718004038', - '10.1017/S1368980018001258', + '10.1017/s0033291718004038', + '10.1017/s1368980018001258', ] }, titles: [ diff --git a/e2e/package-lock.json b/e2e/package-lock.json index c6be6b6c2..d8e5b0480 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -9,23 +9,23 @@ "version": "1.0.0", "license": "Apache 2.0", "devDependencies": { - "@playwright/test": "1.28.1", - "@types/node": "18.11.11", - "@typescript-eslint/eslint-plugin": "5.46.0", - "@typescript-eslint/parser": "5.46.0", - "eslint": "8.29.0" + "@playwright/test": "1.30.0", + "@types/node": "18.11.18", + "@typescript-eslint/eslint-plugin": "5.50.0", + "@typescript-eslint/parser": "5.50.0", + "eslint": "8.33.0" } }, "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -40,9 +40,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -108,13 +108,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", - "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.30.0.tgz", + "integrity": "sha512-SVxkQw1xvn/Wk/EvBnqWIq6NLo1AppwbYOjNLmyU0R1RoQ3rLEBtmjTnElcnz8VEtn11fptj1ECxK0tgURhajw==", "dev": true, "dependencies": { "@types/node": "*", - "playwright-core": "1.28.1" + "playwright-core": "1.30.0" }, "bin": { "playwright": "cli.js" @@ -130,9 +130,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", - "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==", + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, "node_modules/@types/semver": { @@ -142,15 +142,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.0.tgz", - "integrity": "sha512-QrZqaIOzJAjv0sfjY4EjbXUi3ZOFpKfzntx22gPGr9pmFcTjcFw/1sS1LJhEubfAGwuLjNrPV0rH+D1/XZFy7Q==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz", + "integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.46.0", - "@typescript-eslint/type-utils": "5.46.0", - "@typescript-eslint/utils": "5.46.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/type-utils": "5.50.0", + "@typescript-eslint/utils": "5.50.0", "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", @@ -175,14 +176,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.0.tgz", - "integrity": "sha512-joNO6zMGUZg+C73vwrKXCd8usnsmOYmgW/w5ZW0pG0RGvqeznjtGDk61EqqTpNrFLUYBW2RSBFrxdAZMqA4OZA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.50.0.tgz", + "integrity": "sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.46.0", - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/typescript-estree": "5.46.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "debug": "^4.3.4" }, "engines": { @@ -202,13 +203,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.0.tgz", - "integrity": "sha512-7wWBq9d/GbPiIM6SqPK9tfynNxVbfpihoY5cSFMer19OYUA3l4powA2uv0AV2eAZV6KoAh6lkzxv4PoxOLh1oA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz", + "integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/visitor-keys": "5.46.0" + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -219,13 +220,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.0.tgz", - "integrity": "sha512-dwv4nimVIAsVS2dTA0MekkWaRnoYNXY26dKz8AN5W3cBFYwYGFQEqm/cG+TOoooKlncJS4RTbFKgcFY/pOiBCg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz", + "integrity": "sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.46.0", - "@typescript-eslint/utils": "5.46.0", + "@typescript-eslint/typescript-estree": "5.50.0", + "@typescript-eslint/utils": "5.50.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -246,9 +247,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.0.tgz", - "integrity": "sha512-wHWgQHFB+qh6bu0IAPAJCdeCdI0wwzZnnWThlmHNY01XJ9Z97oKqKOzWYpR2I83QmshhQJl6LDM9TqMiMwJBTw==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz", + "integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -259,13 +260,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.0.tgz", - "integrity": "sha512-kDLNn/tQP+Yp8Ro2dUpyyVV0Ksn2rmpPpB0/3MO874RNmXtypMwSeazjEN/Q6CTp8D7ExXAAekPEcCEB/vtJkw==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz", + "integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/visitor-keys": "5.46.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -286,16 +287,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.0.tgz", - "integrity": "sha512-4O+Ps1CRDw+D+R40JYh5GlKLQERXRKW5yIQoNDpmXPJ+C7kaPF9R7GWl+PxGgXjB3PQCqsaaZUpZ9dG4U6DO7g==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz", + "integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.46.0", - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/typescript-estree": "5.46.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -312,12 +313,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.0.tgz", - "integrity": "sha512-E13gBoIXmaNhwjipuvQg1ByqSAu/GbEpP/qzFihugJ+MomtoJtFAJG/+2DRPByf57B863m0/q7Zt16V9ohhANw==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz", + "integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.46.0", + "@typescript-eslint/types": "5.50.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -329,9 +330,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -555,13 +556,13 @@ } }, "node_modules/eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -580,7 +581,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -805,9 +806,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -911,9 +912,9 @@ } }, "node_modules/globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -961,9 +962,9 @@ } }, "node_modules/ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -1056,9 +1057,9 @@ "dev": true }, "node_modules/js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", "dev": true, "funding": { "type": "opencollective", @@ -1304,9 +1305,9 @@ } }, "node_modules/playwright-core": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", - "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", + "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==", "dev": true, "bin": { "playwright": "cli.js" @@ -1325,9 +1326,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, "engines": { "node": ">=6" @@ -1567,9 +1568,9 @@ } }, "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "peer": true, "bin": { @@ -1640,15 +1641,15 @@ }, "dependencies": { "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -1657,9 +1658,9 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -1706,13 +1707,13 @@ } }, "@playwright/test": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", - "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.30.0.tgz", + "integrity": "sha512-SVxkQw1xvn/Wk/EvBnqWIq6NLo1AppwbYOjNLmyU0R1RoQ3rLEBtmjTnElcnz8VEtn11fptj1ECxK0tgURhajw==", "dev": true, "requires": { "@types/node": "*", - "playwright-core": "1.28.1" + "playwright-core": "1.30.0" } }, "@types/json-schema": { @@ -1722,9 +1723,9 @@ "dev": true }, "@types/node": { - "version": "18.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", - "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==", + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, "@types/semver": { @@ -1734,15 +1735,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.0.tgz", - "integrity": "sha512-QrZqaIOzJAjv0sfjY4EjbXUi3ZOFpKfzntx22gPGr9pmFcTjcFw/1sS1LJhEubfAGwuLjNrPV0rH+D1/XZFy7Q==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz", + "integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.46.0", - "@typescript-eslint/type-utils": "5.46.0", - "@typescript-eslint/utils": "5.46.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/type-utils": "5.50.0", + "@typescript-eslint/utils": "5.50.0", "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", @@ -1751,53 +1753,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.0.tgz", - "integrity": "sha512-joNO6zMGUZg+C73vwrKXCd8usnsmOYmgW/w5ZW0pG0RGvqeznjtGDk61EqqTpNrFLUYBW2RSBFrxdAZMqA4OZA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.50.0.tgz", + "integrity": "sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.46.0", - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/typescript-estree": "5.46.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.0.tgz", - "integrity": "sha512-7wWBq9d/GbPiIM6SqPK9tfynNxVbfpihoY5cSFMer19OYUA3l4powA2uv0AV2eAZV6KoAh6lkzxv4PoxOLh1oA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz", + "integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/visitor-keys": "5.46.0" + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0" } }, "@typescript-eslint/type-utils": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.0.tgz", - "integrity": "sha512-dwv4nimVIAsVS2dTA0MekkWaRnoYNXY26dKz8AN5W3cBFYwYGFQEqm/cG+TOoooKlncJS4RTbFKgcFY/pOiBCg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz", + "integrity": "sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.46.0", - "@typescript-eslint/utils": "5.46.0", + "@typescript-eslint/typescript-estree": "5.50.0", + "@typescript-eslint/utils": "5.50.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.0.tgz", - "integrity": "sha512-wHWgQHFB+qh6bu0IAPAJCdeCdI0wwzZnnWThlmHNY01XJ9Z97oKqKOzWYpR2I83QmshhQJl6LDM9TqMiMwJBTw==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz", + "integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.0.tgz", - "integrity": "sha512-kDLNn/tQP+Yp8Ro2dUpyyVV0Ksn2rmpPpB0/3MO874RNmXtypMwSeazjEN/Q6CTp8D7ExXAAekPEcCEB/vtJkw==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz", + "integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==", "dev": true, "requires": { - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/visitor-keys": "5.46.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1806,35 +1808,35 @@ } }, "@typescript-eslint/utils": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.0.tgz", - "integrity": "sha512-4O+Ps1CRDw+D+R40JYh5GlKLQERXRKW5yIQoNDpmXPJ+C7kaPF9R7GWl+PxGgXjB3PQCqsaaZUpZ9dG4U6DO7g==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz", + "integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.46.0", - "@typescript-eslint/types": "5.46.0", - "@typescript-eslint/typescript-estree": "5.46.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.0.tgz", - "integrity": "sha512-E13gBoIXmaNhwjipuvQg1ByqSAu/GbEpP/qzFihugJ+MomtoJtFAJG/+2DRPByf57B863m0/q7Zt16V9ohhANw==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz", + "integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.46.0", + "@typescript-eslint/types": "5.50.0", "eslint-visitor-keys": "^3.3.0" } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, "acorn-jsx": { @@ -1996,13 +1998,13 @@ "dev": true }, "eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -2021,7 +2023,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -2193,9 +2195,9 @@ "dev": true }, "fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -2275,9 +2277,9 @@ } }, "globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -2310,9 +2312,9 @@ "dev": true }, "ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "import-fresh": { @@ -2381,9 +2383,9 @@ "dev": true }, "js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", "dev": true }, "js-yaml": { @@ -2565,9 +2567,9 @@ "dev": true }, "playwright-core": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", - "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", + "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==", "dev": true }, "prelude-ls": { @@ -2577,9 +2579,9 @@ "dev": true }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, "queue-microtask": { @@ -2724,9 +2726,9 @@ "dev": true }, "typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "peer": true }, diff --git a/e2e/package-lock.json.license b/e2e/package-lock.json.license index 1a696ef30..07b430d87 100644 --- a/e2e/package-lock.json.license +++ b/e2e/package-lock.json.license @@ -1,4 +1,5 @@ +SPDX-FileCopyrightText: 2022 - 2023 dv4all SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -SPDX-FileCopyrightText: 2022 dv4all +SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) SPDX-License-Identifier: Apache-2.0 diff --git a/e2e/package.json b/e2e/package.json index 46303210e..3156e2dbe 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -31,10 +31,10 @@ "author": "Dusan Mijatovic", "license": "Apache 2.0", "devDependencies": { - "@playwright/test": "1.28.1", - "@types/node": "18.11.11", - "@typescript-eslint/eslint-plugin": "5.46.0", - "@typescript-eslint/parser": "5.46.0", - "eslint": "8.29.0" + "@playwright/test": "1.30.0", + "@types/node": "18.11.18", + "@typescript-eslint/eslint-plugin": "5.50.0", + "@typescript-eslint/parser": "5.50.0", + "eslint": "8.33.0" } } diff --git a/e2e/package.json.license b/e2e/package.json.license index 9655c0512..871b7b5a0 100644 --- a/e2e/package.json.license +++ b/e2e/package.json.license @@ -1,5 +1,5 @@ -SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +SPDX-FileCopyrightText: 2022 - 2023 dv4all SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -SPDX-FileCopyrightText: 2022 dv4all SPDX-License-Identifier: Apache-2.0 diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index a84d7d453..1eee68058 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -32,6 +32,16 @@ "no-multi-spaces": [ "warn" ], - "no-multiple-empty-lines": "warn" + "no-multiple-empty-lines": "warn", + // use direct imports on material-ui to improve + // performance in unit tests with jest + // see https://blog.bitsrc.io/why-is-my-jest-suite-so-slow-2a4859bb9ac0 + "no-restricted-imports": [ + "warn", + { + "name": "@mui/material", + "message": "Please use \"import foo from '@mui/material/foo'\" instead." + } + ] } } \ No newline at end of file diff --git a/frontend/.eslintrc.json.license b/frontend/.eslintrc.json.license index 99b774258..4ca859ffb 100644 --- a/frontend/.eslintrc.json.license +++ b/frontend/.eslintrc.json.license @@ -1,4 +1,5 @@ -SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -SPDX-FileCopyrightText: 2021 - 2022 dv4all +SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +SPDX-FileCopyrightText: 2021 - 2023 dv4all +SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: CC-BY-4.0 diff --git a/frontend/__tests__/OrganisationItem.test.tsx b/frontend/__tests__/OrganisationItem.test.tsx index a8d734f44..2f825001e 100644 --- a/frontend/__tests__/OrganisationItem.test.tsx +++ b/frontend/__tests__/OrganisationItem.test.tsx @@ -1,6 +1,6 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -16,6 +16,9 @@ import mockSoftware from '~/components/organisation/software/__mocks__/mockSoftw import mockProjects from '~/components/organisation/projects/__mocks__/mockProjects.json' import mockUnits from '~/components/organisation/units/__mocks__/mockUnits.json' +// MOCK user agreement call +jest.mock('~/components/user/settings/fetchAgreementStatus') + const mockProps = { organisation: mockOrganisation, slug:[ diff --git a/frontend/__tests__/ProjectEditPage.test.tsx b/frontend/__tests__/ProjectEditPage.test.tsx index 26c366c19..82ef0229b 100644 --- a/frontend/__tests__/ProjectEditPage.test.tsx +++ b/frontend/__tests__/ProjectEditPage.test.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 dv4all // @@ -17,6 +18,8 @@ import mockProjectToEdit from '~/components/projects/edit/information/__mocks__/ // MOCKS // we mock default providers used in page header jest.mock('~/auth/api/useLoginProviders') +// mock user agreement call +jest.mock('~/components/user/settings/fetchAgreementStatus') // MOCK isMaintainerOf const mockIsMaintainer = jest.fn(props => Promise.resolve(false)) diff --git a/frontend/__tests__/SoftwareEditPage.test.tsx b/frontend/__tests__/SoftwareEditPage.test.tsx index 411bfaeb4..adeafddab 100644 --- a/frontend/__tests__/SoftwareEditPage.test.tsx +++ b/frontend/__tests__/SoftwareEditPage.test.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 dv4all // @@ -17,6 +18,9 @@ import {softwareInformation as config} from '~/components/software/edit/editSoft // MOCKS // we mock default providers used in page header jest.mock('~/auth/api/useLoginProviders') +// mock user agreement call +jest.mock('~/components/user/settings/fetchAgreementStatus') + // MOCK isMaintainerOf const mockIsMaintainer = jest.fn(props => Promise.resolve(false)) diff --git a/frontend/__tests__/UserPages.test.tsx b/frontend/__tests__/UserPages.test.tsx index b26a77d5f..889e5a467 100644 --- a/frontend/__tests__/UserPages.test.tsx +++ b/frontend/__tests__/UserPages.test.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 dv4all // @@ -8,8 +9,12 @@ import {WithAppContext, mockSession} from '~/utils/jest/WithAppContext' import UserPages from '../pages/user/[section]' +// MOCK user agreement call +jest.mock('~/components/user/settings/fetchAgreementStatus') + +// MOCKS const mockProps = { - section: 'profile', + section: 'software', counts: { software_cnt: 0, project_cnt: 0, @@ -36,7 +41,7 @@ describe('pages/user/[section].tsx', () => { }) it('renders user nav items', () => { - mockProps.section = 'profile' + mockProps.section = 'software' render( @@ -47,8 +52,8 @@ describe('pages/user/[section].tsx', () => { expect(navItems.length).toEqual(4) }) - it('renders user profile section', async() => { - mockProps.section = 'profile' + it('renders user settings section', async() => { + mockProps.section = 'settings' render( @@ -75,4 +80,16 @@ describe('pages/user/[section].tsx', () => { const loader = screen.getByRole('progressbar') }) + it('renders user projects section', async() => { + mockProps.section = 'projects' + + const {container} = render( + + + + ) + + const loader = screen.getByRole('progressbar') + }) + }) diff --git a/frontend/components/AppHeader/AddMenu.tsx b/frontend/components/AppHeader/AddMenu.tsx index cf721c5a8..378d6fa0f 100644 --- a/frontend/components/AppHeader/AddMenu.tsx +++ b/frontend/components/AppHeader/AddMenu.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -10,7 +10,8 @@ import MenuItem from '@mui/material/MenuItem' import AddIcon from '@mui/icons-material/Add' import TerminalIcon from '@mui/icons-material/Terminal' import ListAltIcon from '@mui/icons-material/ListAlt' -import {IconButton, ListItemIcon} from '@mui/material' +import IconButton from '@mui/material/IconButton' +import ListItemIcon from '@mui/material/ListItemIcon' import CaretIcon from '~/components/icons/caret.svg' import useDisableScrollLock from '~/utils/useDisableScrollLock' diff --git a/frontend/components/AppHeader/index.tsx b/frontend/components/AppHeader/index.tsx index af34cc047..b6b9aa344 100644 --- a/frontend/components/AppHeader/index.tsx +++ b/frontend/components/AppHeader/index.tsx @@ -1,12 +1,14 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Jesús García Gonzalez (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 import {useState, useEffect, MouseEvent} from 'react' -import {IconButton, Menu, MenuItem} from '@mui/material' +import IconButton from '@mui/material/IconButton' +import Menu from '@mui/material/Menu' +import MenuItem from '@mui/material/MenuItem' import MenuIcon from '@mui/icons-material/Menu' import Link from 'next/link' // local dependencies (project components) diff --git a/frontend/components/charts/d3LineChart/NoDataAvailableChart.tsx b/frontend/components/charts/d3LineChart/NoDataAvailableChart.tsx index 390985fb5..65b20395b 100644 --- a/frontend/components/charts/d3LineChart/NoDataAvailableChart.tsx +++ b/frontend/components/charts/d3LineChart/NoDataAvailableChart.tsx @@ -1,6 +1,6 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // @@ -10,7 +10,7 @@ import * as d3 from 'd3' import {useRef, useEffect, useState} from 'react' import useResizeObserver,{SizeType} from './useResizeObserver' import logger from '../../../utils/logger' -import {useTheme} from '@mui/material' +import useTheme from '@mui/material/styles/useTheme' export type Point = {x: number, y: number} diff --git a/frontend/components/charts/d3LineChart/SingleLineChart.tsx b/frontend/components/charts/d3LineChart/SingleLineChart.tsx index f978e7999..f8c0a0726 100644 --- a/frontend/components/charts/d3LineChart/SingleLineChart.tsx +++ b/frontend/components/charts/d3LineChart/SingleLineChart.tsx @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 import {useRef, useEffect, useState} from 'react' -import {useTheme} from '@mui/material' +import useTheme from '@mui/material/styles/useTheme' import useResizeObserver from './useResizeObserver' import drawLineChart from './drawLineChart' diff --git a/frontend/components/cookies/CookieConsentMatomo.tsx b/frontend/components/cookies/CookieConsentMatomo.tsx index c15001e3f..00f2826c4 100644 --- a/frontend/components/cookies/CookieConsentMatomo.tsx +++ b/frontend/components/cookies/CookieConsentMatomo.tsx @@ -1,6 +1,6 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -11,6 +11,7 @@ import {Matomo} from './nodeCookies' import {useMatomoConsent} from './useCookieConsent' import Link from 'next/link' import CookieTwoToneIcon from '@mui/icons-material/CookieTwoTone' +import logger from '~/utils/logger' type CookieConsentMatomoProps = { matomo: Matomo, @@ -31,10 +32,16 @@ export default function CookieConsentMatomo({matomo, route}: CookieConsentMatomo // console.log('route...', route) // console.log('open...', open) // console.groupEnd() + useEffect(() => { - if (localStorage) { - const cookieConsent = localStorage.getItem('rsd_cookies_consent') - setOpen(cookieConsent === null && matomo.id !== null && matomo.consent === null && route !== '/cookies') + try { + if (localStorage) { + const cookieConsent = localStorage.getItem('rsd_cookies_consent') + setOpen(cookieConsent === null && matomo.id !== null && matomo.consent === null && route !== '/cookies') + } + } catch (e: any) { + // just show info log + logger(`CookieConsentMatomo.useEffect...${e.message}`,'info') } },[matomo.id,matomo.consent,route]) diff --git a/frontend/components/form/AsyncAutocompleteSC.tsx b/frontend/components/form/AsyncAutocompleteSC.tsx index 4e37e090a..afc594063 100644 --- a/frontend/components/form/AsyncAutocompleteSC.tsx +++ b/frontend/components/form/AsyncAutocompleteSC.tsx @@ -1,5 +1,5 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) @@ -10,7 +10,9 @@ import {useState, ReactNode, HTMLAttributes, SyntheticEvent, useEffect} from 'react' import Autocomplete, {AutocompleteChangeReason, AutocompleteInputChangeReason, AutocompleteRenderOptionState} from '@mui/material/Autocomplete' -import {CircularProgress, FilterOptionsState, TextField} from '@mui/material' +import CircularProgress from '@mui/material/CircularProgress' +import {FilterOptionsState} from '@mui/material/useAutocomplete' +import TextField from '@mui/material/TextField' import {useDebounce} from '~/utils/useDebounce' diff --git a/frontend/components/form/ControlledSlugTextField.tsx b/frontend/components/form/ControlledSlugTextField.tsx index 23a5d16f9..68f1087c3 100644 --- a/frontend/components/form/ControlledSlugTextField.tsx +++ b/frontend/components/form/ControlledSlugTextField.tsx @@ -1,12 +1,12 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 import MuiTextField from '@mui/material/TextField' import InputAdornment from '@mui/material/InputAdornment' import {styled} from '@mui/material/styles' -import {CircularProgress} from '@mui/material' +import CircularProgress from '@mui/material/CircularProgress' import {Controller} from 'react-hook-form' const TextField = styled(MuiTextField)(({theme}) => ({ diff --git a/frontend/components/form/ControlledSwitch.tsx b/frontend/components/form/ControlledSwitch.tsx index e0c17a73f..908fec278 100644 --- a/frontend/components/form/ControlledSwitch.tsx +++ b/frontend/components/form/ControlledSwitch.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -30,6 +30,7 @@ export default function ControlledSwitch({label, name, // console.log('ControlledSwitch.value...', value) return ( ({ width: '100%', diff --git a/frontend/components/layout/ConfirmDeleteModal.tsx b/frontend/components/layout/ConfirmDeleteModal.tsx index 0314e3f35..07958b7e0 100644 --- a/frontend/components/layout/ConfirmDeleteModal.tsx +++ b/frontend/components/layout/ConfirmDeleteModal.tsx @@ -1,14 +1,15 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 -import { - Button, - Dialog, DialogActions, DialogContent, - DialogTitle, useMediaQuery -} from '@mui/material' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' import DeleteIcon from '@mui/icons-material/Delete' import WarningIcon from '@mui/icons-material/Warning' diff --git a/frontend/components/layout/ContributorPrivacyHint.tsx b/frontend/components/layout/ContributorPrivacyHint.tsx new file mode 100644 index 000000000..e29b57660 --- /dev/null +++ b/frontend/components/layout/ContributorPrivacyHint.tsx @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import Alert from '@mui/material/Alert' +import Link from 'next/link' +import useRsdSettings from '~/config/useRsdSettings' + +export default function ContributorPrivacyHint() { + const {host} = useRsdSettings() + return ( + + Before adding an individual, make sure to ask for their permission to appear in the RSD. Please see our Terms of Service for more information. + + ) +} diff --git a/frontend/components/layout/NoContent.tsx b/frontend/components/layout/NoContent.tsx index 11a0cd706..f1c5f6a27 100644 --- a/frontend/components/layout/NoContent.tsx +++ b/frontend/components/layout/NoContent.tsx @@ -1,14 +1,16 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 import {useEffect, useRef, useState} from 'react' import styled from '@emotion/styled' import DoDisturbIcon from '@mui/icons-material/DoDisturb' -import {Box, Theme} from '@mui/material' +import Box from '@mui/material/Box' +import {Theme} from '@mui/material/styles/createTheme' import Slide from '@mui/material/Slide' + const NoContentText = styled('h2')(({theme}:{theme?:Theme}) => ({ fontWeight: 500, letterSpacing: '0.25rem', diff --git a/frontend/components/layout/SortableList.tsx b/frontend/components/layout/SortableList.tsx index 5d9c7be1a..ebd2e312f 100644 --- a/frontend/components/layout/SortableList.tsx +++ b/frontend/components/layout/SortableList.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -9,7 +9,7 @@ import { } from '@dnd-kit/core' import {restrictToParentElement, restrictToVerticalAxis} from '@dnd-kit/modifiers' import {arrayMove, SortableContext, verticalListSortingStrategy} from '@dnd-kit/sortable' -import {List} from '@mui/material' +import List from '@mui/material/List' type RequiredProps = { id: string | null, diff --git a/frontend/components/layout/UserMenu.tsx b/frontend/components/layout/UserMenu.tsx index 6d7a49a7d..cea7a5832 100644 --- a/frontend/components/layout/UserMenu.tsx +++ b/frontend/components/layout/UserMenu.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -9,7 +9,8 @@ import IconButton from '@mui/material/IconButton' import Menu from '@mui/material/Menu' import MenuItem from '@mui/material/MenuItem' import Avatar from '@mui/material/Avatar' -import {Divider, ListItemIcon} from '@mui/material' +import Divider from '@mui/material/Divider' +import ListItemIcon from '@mui/material/ListItemIcon' import {useAuth} from '../../auth/index' import {MenuItemType} from '../../config/menuItems' diff --git a/frontend/components/mention/EditMentionModal.tsx b/frontend/components/mention/EditMentionModal.tsx index 651cd929c..327fe9f4a 100644 --- a/frontend/components/mention/EditMentionModal.tsx +++ b/frontend/components/mention/EditMentionModal.tsx @@ -1,17 +1,19 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Netherlands eScience Center -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 import {useEffect} from 'react' -import { - Button, Dialog, DialogActions, DialogContent, - DialogTitle, useMediaQuery -} from '@mui/material' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' import Alert from '@mui/material/Alert' // import AlertTitle from '@mui/material/AlertTitle' diff --git a/frontend/components/mention/MentionEditFeatured.tsx b/frontend/components/mention/MentionEditFeatured.tsx index 595630865..48ec2da24 100644 --- a/frontend/components/mention/MentionEditFeatured.tsx +++ b/frontend/components/mention/MentionEditFeatured.tsx @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 -import {IconButton} from '@mui/material' +import IconButton from '@mui/material/IconButton' import EditIcon from '@mui/icons-material/Edit' import DeleteIcon from '@mui/icons-material/Delete' import {useSession} from '~/auth' diff --git a/frontend/components/mention/MentionEditItem.tsx b/frontend/components/mention/MentionEditItem.tsx index 24bae511e..d1e384233 100644 --- a/frontend/components/mention/MentionEditItem.tsx +++ b/frontend/components/mention/MentionEditItem.tsx @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 -import {IconButton} from '@mui/material' +import IconButton from '@mui/material/IconButton' import EditIcon from '@mui/icons-material/Edit' import DeleteIcon from '@mui/icons-material/Delete' // import UpdateIcon from '@mui/icons-material/Update' diff --git a/frontend/components/organisation/OrganisationNavItems.tsx b/frontend/components/organisation/OrganisationNavItems.tsx index d50c7955a..5ff9e6b63 100644 --- a/frontend/components/organisation/OrganisationNavItems.tsx +++ b/frontend/components/organisation/OrganisationNavItems.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -9,6 +9,7 @@ import AccountTreeIcon from '@mui/icons-material/AccountTree' import ListAltIcon from '@mui/icons-material/ListAlt' import PersonIcon from '@mui/icons-material/Person' import SettingsIcon from '@mui/icons-material/Settings' +import StyleOutlinedIcon from '@mui/icons-material/StyleOutlined' import AboutOrganisation from './about' import OrganisationSoftware from './software' @@ -17,6 +18,7 @@ import OrganisationUnits from './units' import OrganisationMaintainers from './maintainers' import OrganisationSettings from './settings' import {OrganisationForOverview} from '~/types/Organisation' +import SoftwareReleases from './releases' export type OrganisationComponentsProps = { organisation: OrganisationForOverview, @@ -61,6 +63,15 @@ export const organisationMenu: OrganisationMenuProps[] = [ isVisible: (props) => true, showSearch: true }, + { + id:'releases', + label:({release_cnt})=>`Releases (${release_cnt ?? 0})`, + icon: , + component: (props) => , + status: 'Software releases', + isVisible: (props) => true, + showSearch: false + }, { id:'projects', label: ({project_cnt})=>`Projects (${project_cnt ?? 0})`, diff --git a/frontend/components/organisation/about/index.tsx b/frontend/components/organisation/about/index.tsx index 9109e66ad..a2d695665 100644 --- a/frontend/components/organisation/about/index.tsx +++ b/frontend/components/organisation/about/index.tsx @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 import {useRouter} from 'next/router' -import {Alert, AlertTitle} from '@mui/material' +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' import ReactMarkdownWithSettings from '~/components/layout/ReactMarkdownWithSettings' import {OrganisationComponentsProps} from '../OrganisationNavItems' diff --git a/frontend/components/organisation/maintainers/OrganisationMaintainersIndex.test.tsx b/frontend/components/organisation/maintainers/OrganisationMaintainersIndex.test.tsx index 89edde14a..3a064aa12 100644 --- a/frontend/components/organisation/maintainers/OrganisationMaintainersIndex.test.tsx +++ b/frontend/components/organisation/maintainers/OrganisationMaintainersIndex.test.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -10,6 +10,9 @@ import {defaultSession} from '~/auth' import OrganisationMaintainers from './index' import mockOrganisation from '../__mocks__/mockOrganisation' +// MOCK user agreement call +jest.mock('~/components/user/settings/fetchAgreementStatus') + const mockMaintainerOfOrganisation=jest.fn,any[]>((props)=>Promise.resolve([])) jest.mock('./getMaintainersOfOrganisation', () => ({ getMaintainersOfOrganisation:jest.fn((props)=>mockMaintainerOfOrganisation(props)) @@ -71,7 +74,7 @@ describe('frontend/components/organisation/maintainers/index.tsx', () => { }) }) - it('shows 403 when user is not organisation maintainer', async() => { + it('shows 403 when user is not organisation maintainer', async () => { // user is authenticated defaultSession.status = 'authenticated' defaultSession.token = 'test-token' @@ -83,12 +86,12 @@ describe('frontend/components/organisation/maintainers/index.tsx', () => { ) const msg403 = await screen.findByRole('heading', { - name:'403' + name: '403' }) expect(msg403).toBeInTheDocument() }) - it('shows "No maintainers" message', async() => { + it('shows "No maintainers" message', async () => { // user is authenticated defaultSession.status = 'authenticated' defaultSession.token = 'test-token' @@ -109,7 +112,7 @@ describe('frontend/components/organisation/maintainers/index.tsx', () => { it('shows maintainer list item', async () => { // MOCK maintainers call - const dummyRawMaintainers= [{ + const dummyRawMaintainers = [{ 'maintainer': 'a050aaf3-9c46-490c-ade3-aeeb6a05b1d1', 'name': ['Jordan Ross Belfort'], 'email': ['Jordan.Belfort@harvard-example.edu'], @@ -130,7 +133,7 @@ describe('frontend/components/organisation/maintainers/index.tsx', () => { ) - await waitForElementToBeRemoved(()=>screen.getByRole('progressbar')) + await waitForElementToBeRemoved(() => screen.getByRole('progressbar')) const maintainer = await screen.findByText(dummyRawMaintainers[0].name[0]) expect(maintainer).toBeInTheDocument() @@ -194,4 +197,5 @@ describe('frontend/components/organisation/maintainers/index.tsx', () => { const maintainerItem = screen.getAllByTestId('maintainer-list-item') expect(maintainerItem.length).toEqual(mockMaintainers.length) }) + }) diff --git a/frontend/components/organisation/maintainers/index.tsx b/frontend/components/organisation/maintainers/index.tsx index 4727fec24..b5850d51a 100644 --- a/frontend/components/organisation/maintainers/index.tsx +++ b/frontend/components/organisation/maintainers/index.tsx @@ -1,7 +1,9 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -20,6 +22,7 @@ import OrganisationMaintainerLink from './OrganisationMaintainerLink' import {OrganisationForOverview} from '~/types/Organisation' import OrganisationMaintainersList from './OrganisationMaintainersList' import ProtectedOrganisationPage from '../ProtectedOrganisationPage' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' type DeleteModal = { open: boolean, @@ -96,6 +99,7 @@ export default function OrganisationMaintainers({organisation, isMaintainer}: +
+// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -14,6 +16,7 @@ import ProjectCardWithMenu from './ProjectCardWithMenu' import ProjectCard from '~/components/projects/ProjectCard' import {OrganisationComponentsProps} from '../OrganisationNavItems' import ContentLoader from '~/components/layout/ContentLoader' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' export default function OrganisationProjects({organisation, isMaintainer}:OrganisationComponentsProps) { const {token} = useSession() @@ -57,6 +60,8 @@ export default function OrganisationProjects({organisation, isMaintainer}:Organi maxWidth={maxWidth} className="gap-[0.125rem] p-[0.125rem] pt-2 pb-12" > + {/* Only when maintainer */} + {isMaintainer && } {projects.map(item => { if (isMaintainer) { return ( diff --git a/frontend/components/organisation/releases/ReleaseItem.tsx b/frontend/components/organisation/releases/ReleaseItem.tsx new file mode 100644 index 000000000..657b729d6 --- /dev/null +++ b/frontend/components/organisation/releases/ReleaseItem.tsx @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import Chip from '@mui/material/Chip' +import OpenInNewIcon from '@mui/icons-material/OpenInNew' +import Link from 'next/link' +import {SoftwareReleaseInfo} from './useSoftwareReleases' + +export default function ReleaseItem({release}: { release: SoftwareReleaseInfo }) { + const releaseDate = new Date(release.release_date) + return ( +
+
+ {/* release date */} +
{ + releaseDate.toLocaleDateString(undefined, { + day: '2-digit', + month: 'short', + year: 'numeric' + })} +
+ +
+ {/* name and version */} +
+ + {release.software_name} + + + + } + size="small" + clickable + sx={{ + maxWidth: '15rem', + borderRadius: '0rem 0.5rem', + // textTransform: 'capitalize', + '& .MuiChip-icon': { + order: 1, + margin:'0rem 0.25rem 0rem 0rem', + height: '1rem', + width: '1rem' + } + }} + /> + +
+ {/* authors, contributors */} + {release.release_authors.length > 0 ? +
{ release.release_authors}
+ : null + } + {/* DOI */} +
+ DOI: {release.release_doi} +
+
+
+
+ ) +} diff --git a/frontend/components/organisation/releases/ReleaseNavButton.tsx b/frontend/components/organisation/releases/ReleaseNavButton.tsx new file mode 100644 index 000000000..3b18cb47b --- /dev/null +++ b/frontend/components/organisation/releases/ReleaseNavButton.tsx @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +type ReleaseNavProps = { + year: string, + release_cnt: number +} + +export default function ReleaseNavButton({year,release_cnt}:ReleaseNavProps) { + return ( + +
+
{year}
+
{release_cnt}
+
+
+ ) +} diff --git a/frontend/components/organisation/releases/ReleaseYear.tsx b/frontend/components/organisation/releases/ReleaseYear.tsx new file mode 100644 index 000000000..ac5087c86 --- /dev/null +++ b/frontend/components/organisation/releases/ReleaseYear.tsx @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import ReleaseItem from './ReleaseItem' +import {SoftwareReleaseInfo} from './useSoftwareReleases' + +type ReleaseYearProps = { + year: string, + releases: SoftwareReleaseInfo[] +} + +const smoothScrollSection = { + padding: '0.5rem 0rem', + // TODO! make this dynamic - for mobiles + scrollMarginTop: '7rem' +} + + +export default function ReleaseYear({year,releases}:ReleaseYearProps) { + return ( +
+
+

+ {year} +

+
+ {releases.length} {releases.length === 1 ? 'release' : 'releases'} +
+
+
+ {releases.map(release=>)} +
+
+ ) +} diff --git a/frontend/components/organisation/releases/ScrollToTopButton.tsx b/frontend/components/organisation/releases/ScrollToTopButton.tsx new file mode 100644 index 000000000..bc972df49 --- /dev/null +++ b/frontend/components/organisation/releases/ScrollToTopButton.tsx @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {useCallback, useEffect, useState} from 'react' +import Fab from '@mui/material/Fab' +import NavigationIcon from '@mui/icons-material/Navigation' + +type ScrollToTopProps = { + minOffset: number, + sx?: any +} + +export default function ScrollToTopButton({minOffset, sx}:ScrollToTopProps) { + const [show, setShow] = useState(false) + + const handleScroll = useCallback(() => { + const position = window.pageYOffset + if (minOffset < position) { + setShow(true) + } + if (show === true && minOffset >= position) { + setShow(false) + } + }, [minOffset, show]) + + useEffect(() => { + window.addEventListener('scroll', handleScroll, {passive: true}) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, [handleScroll]) + + // return null when not to show + if (show===false) return null + + return ( + window.scrollTo(0,0)} + sx={{ + position: 'fixed', + right: '3rem', + ...sx ?? {bottom: '3rem'} + }} + > + + + ) +} diff --git a/frontend/components/organisation/releases/index.tsx b/frontend/components/organisation/releases/index.tsx new file mode 100644 index 000000000..9c5183250 --- /dev/null +++ b/frontend/components/organisation/releases/index.tsx @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {useSession} from '~/auth' +import ContentLoader from '~/components/layout/ContentLoader' +import {OrganisationComponentsProps} from '../OrganisationNavItems' +import useSoftwareRelease from './useSoftwareReleases' +import ReleaseYear from './ReleaseYear' +import NoContent from '~/components/layout/NoContent' +import ReleaseNavButton from './ReleaseNavButton' +import ScrollToTopButton from './ScrollToTopButton' + +export default function SoftwareReleases({organisation, isMaintainer}: OrganisationComponentsProps) { + const {token} = useSession() + const {loading, releases} = useSoftwareRelease({ + organisation_slug: organisation.slug, + token + }) + // extact year keys in descending order + const years = Object.keys(releases ?? {}).sort((a,b)=>parseInt(b) - parseInt(a)) + + // console.group('SoftwareReleases') + // console.log('loading...', loading) + // console.log('releases...', releases) + // console.log('years...', years) + // console.groupEnd() + + // show loader + if (loading===true) return + // no content + if (typeof releases === 'undefined') return + if (years.length === 0) return + // releases per year + return ( +
+

Releases per year

+ + {years + .map(year => { + return + })} + +
+
+ ) +} diff --git a/frontend/components/organisation/releases/useSoftwareReleases.tsx b/frontend/components/organisation/releases/useSoftwareReleases.tsx new file mode 100644 index 000000000..759dd633f --- /dev/null +++ b/frontend/components/organisation/releases/useSoftwareReleases.tsx @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {useEffect, useState} from 'react' +import {createJsonHeaders, getBaseUrl} from '~/utils/fetchHelpers' +import logger from '~/utils/logger' + +export type SoftwareReleaseInfo = { + software_id: string + software_slug: string + software_name: string + release_doi: string + release_tag: string | null + release_date: string + release_year: number + release_authors: string + organisation_slug: string[] +} + +export type SoftwareReleasedByYear = { + [key: string]: SoftwareReleaseInfo[] +} + +type UseSoftwareReleaseProps = { + organisation_slug: string, + token: string +} + + +function softwareReleaseByYear(data: SoftwareReleaseInfo[]) { + const releaseByYear:SoftwareReleasedByYear = {} + + data.forEach(item => { + if (releaseByYear.hasOwnProperty(item.release_year.toString())===false) { + // create new key + releaseByYear[item.release_year.toString()] = [] + } + // parse info + // item.release_info = JSON.parse(item.release_info) + releaseByYear[item.release_year.toString()].push(item) + }) + + return releaseByYear +} + +async function getReleasesForOrganisation({organisation_slug, token}:UseSoftwareReleaseProps) { + try { + const query = `organisation_slug=cs.{${organisation_slug}}&order=release_date.desc` + const url = `${getBaseUrl()}/rpc/software_release?${query}` + // make request + const resp = await fetch(url, { + method: 'GET', + headers: { + ...createJsonHeaders(token) + }, + }) + + if (resp.status === 200) { + const data:SoftwareReleaseInfo[] = await resp.json() + return softwareReleaseByYear(data) + } + // some other errors + logger(`getReleasesForOrganisation...${resp.status} ${resp.statusText}`) + return null + } catch(e:any) { + logger(`getReleasesForOrganisation...error...${e.message}`) + return null + } +} + +export default function useSoftwareRelease({organisation_slug, token}:UseSoftwareReleaseProps) { + const [loading, setLoading] = useState(true) + const [releases, setReleases] = useState() + + + // console.group('useSoftwareRelease') + // console.log('loading...', loading) + // console.log('releases...', releases) + // console.log('organisation_slug...', organisation_slug) + // console.log('token...', token) + // console.groupEnd() + + useEffect(() => { + async function getReleases() { + setLoading(true) + // make request + const releases = await getReleasesForOrganisation({organisation_slug, token}) + // update releases + if (releases) setReleases(releases) + setLoading(false) + } + + if (organisation_slug) { + getReleases() + } + + },[organisation_slug,token]) + + return { + loading, + releases + } +} diff --git a/frontend/components/organisation/settings/index.tsx b/frontend/components/organisation/settings/index.tsx index 8fcdb1ad5..462297e67 100644 --- a/frontend/components/organisation/settings/index.tsx +++ b/frontend/components/organisation/settings/index.tsx @@ -1,7 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -14,6 +14,7 @@ import RorIdWithUpdate from './RorIdWithUpdate' import RsdAdminSection from './RsdAdminSection' import ProtectedOrganisationPage from '../ProtectedOrganisationPage' import AutosaveOrganisationTextField from './AutosaveOrganisationTextField' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' const formId='organisation-settings-form' @@ -40,6 +41,7 @@ export default function OrganisationSettings({organisation, isMaintainer}: +
+// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -14,6 +16,7 @@ import NoContent from '~/components/layout/NoContent' import {OrganisationComponentsProps} from '../OrganisationNavItems' import SoftwareCardWithMenu from './SoftwareCardWithMenu' import ContentLoader from '~/components/layout/ContentLoader' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' export default function OrganisationSoftware({organisation, isMaintainer}: OrganisationComponentsProps) { const {token} = useSession() @@ -51,6 +54,8 @@ export default function OrganisationSoftware({organisation, isMaintainer}: Organ minWidth={minWidth} maxWidth={maxWidth} > + {/* Only when maintainer */} + {isMaintainer && } {software.map(item => { if (isMaintainer) { return( diff --git a/frontend/components/organisation/units/OrganisationUnitsIndex.test.tsx b/frontend/components/organisation/units/OrganisationUnitsIndex.test.tsx index 783d2eec7..8c04cf6a2 100644 --- a/frontend/components/organisation/units/OrganisationUnitsIndex.test.tsx +++ b/frontend/components/organisation/units/OrganisationUnitsIndex.test.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -253,7 +253,7 @@ describe('frontend/components/organisation/software/index.tsx', () => { 'method': 'POST' } await waitFor(() => { - expect(global.fetch).toBeCalledTimes(1) + expect(global.fetch).toBeCalledTimes(2) expect(global.fetch).toBeCalledWith(expectedUrl,expectedPayload) }) }) diff --git a/frontend/components/organisation/units/ResearchUnitList.tsx b/frontend/components/organisation/units/ResearchUnitList.tsx index 371fd84bb..3c9750074 100644 --- a/frontend/components/organisation/units/ResearchUnitList.tsx +++ b/frontend/components/organisation/units/ResearchUnitList.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -7,7 +7,8 @@ * UnitsList component is adjusted OrganisationsList component * from (/components/software/organisations/) */ -import {Alert, AlertTitle} from '@mui/material' +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' import List from '@mui/material/List' import {OrganisationForOverview} from '../../../types/Organisation' diff --git a/frontend/components/organisation/units/ResearchUnitModal.tsx b/frontend/components/organisation/units/ResearchUnitModal.tsx index 82fd8146d..83b28c775 100644 --- a/frontend/components/organisation/units/ResearchUnitModal.tsx +++ b/frontend/components/organisation/units/ResearchUnitModal.tsx @@ -1,17 +1,21 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 import {useEffect, useState} from 'react' -import { - Avatar, - Button, Dialog, DialogActions, DialogContent, - DialogTitle, TextField, useMediaQuery -} from '@mui/material' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' +import Avatar from '@mui/material/Avatar' +import TextField from '@mui/material/TextField' import DeleteIcon from '@mui/icons-material/Delete' + import {useForm} from 'react-hook-form' import {useSession} from '~/auth' diff --git a/frontend/components/organisation/units/index.tsx b/frontend/components/organisation/units/index.tsx index f3f595e01..3e0316997 100644 --- a/frontend/components/organisation/units/index.tsx +++ b/frontend/components/organisation/units/index.tsx @@ -1,5 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -27,6 +29,7 @@ import ResearchUnitModal from './ResearchUnitModal' import {OrganisationComponentsProps} from '../OrganisationNavItems' import {upsertImage} from '~/utils/editImage' import {getPropsFromObject} from '~/utils/getPropsFromObject' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' type EditOrganisationModal = { open: boolean, @@ -34,7 +37,7 @@ type EditOrganisationModal = { organisation?: EditOrganisation } -export default function ResearchUnits({organisation}: OrganisationComponentsProps) { +export default function ResearchUnits({organisation,isMaintainer}: OrganisationComponentsProps) { const {token,user} = useSession() const {showErrorMessage} = useSnackbar() const {units, setUnits, loading} = useOrganisationUnits({ @@ -218,6 +221,8 @@ export default function ResearchUnits({organisation}: OrganisationComponentsProp return (
+ {/* Only when maintainer */} + {isMaintainer && }

Research Units ({units.length ?? 0})

{renderAddBtn()} diff --git a/frontend/components/projects/edit/information/ProjectLinkModal.tsx b/frontend/components/projects/edit/information/ProjectLinkModal.tsx index 2db84ec5d..3234effbb 100644 --- a/frontend/components/projects/edit/information/ProjectLinkModal.tsx +++ b/frontend/components/projects/edit/information/ProjectLinkModal.tsx @@ -1,14 +1,16 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 import SaveIcon from '@mui/icons-material/Save' -import { - Button, Dialog, DialogActions, DialogContent, - DialogTitle, useMediaQuery -} from '@mui/material' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' import {useForm} from 'react-hook-form' diff --git a/frontend/components/projects/edit/organisations/index.tsx b/frontend/components/projects/edit/organisations/index.tsx index 286c48b7d..3db196610 100644 --- a/frontend/components/projects/edit/organisations/index.tsx +++ b/frontend/components/projects/edit/organisations/index.tsx @@ -1,6 +1,6 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -65,7 +65,7 @@ export default function ProjectOrganisations({slug}: { slug: string }) { }) // check if present by ror_id const found = organisations.find(item => item.ror_id === addOrganisation.ror_id) - if (found) { + if (addOrganisation.ror_id && found) { showInfoMessage(`${item.name} is already in the collection (based on ror_id).`) return } diff --git a/frontend/components/projects/edit/related/RelatedProjectList.tsx b/frontend/components/projects/edit/related/RelatedProjectList.tsx index 79663d97f..a82825961 100644 --- a/frontend/components/projects/edit/related/RelatedProjectList.tsx +++ b/frontend/components/projects/edit/related/RelatedProjectList.tsx @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 -import {Alert, AlertTitle} from '@mui/material' +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' import List from '@mui/material/List' import ListItem from '@mui/material/ListItem' import ListItemText from '@mui/material/ListItemText' @@ -101,7 +102,7 @@ export function RelatedProjectItem({project,onDelete}:ProjectItemProps) { } sx={{ - height:itemHeight, + minHeight:itemHeight, // this makes space for buttons paddingRight:'5rem', '&:hover': { diff --git a/frontend/components/projects/edit/related/RelatedSoftwareList.tsx b/frontend/components/projects/edit/related/RelatedSoftwareList.tsx index 7d9a326e9..51f61e9ad 100644 --- a/frontend/components/projects/edit/related/RelatedSoftwareList.tsx +++ b/frontend/components/projects/edit/related/RelatedSoftwareList.tsx @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 -import {Alert, AlertTitle} from '@mui/material' +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' import List from '@mui/material/List' import ListItem from '@mui/material/ListItem' import ListItemText from '@mui/material/ListItemText' @@ -105,7 +106,7 @@ export function RelatedSoftwareItem({software,onDelete}:SoftwareItemProps) { } sx={{ - height:itemHeight, + minHeight:itemHeight, // this makes space for buttons paddingRight:'5rem', '&:hover': { diff --git a/frontend/components/projects/edit/team/EditProjectTeamIndex.test.tsx b/frontend/components/projects/edit/team/EditProjectTeamIndex.test.tsx index 36985c8e0..ec5696328 100644 --- a/frontend/components/projects/edit/team/EditProjectTeamIndex.test.tsx +++ b/frontend/components/projects/edit/team/EditProjectTeamIndex.test.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 dv4all // @@ -99,7 +100,7 @@ describe('frontend/components/projects/edit/team/index.tsx', () => { // wait for loader to be removed await waitForElementToBeRemoved(screen.getByRole('progressbar')) // shows no members alert message - const noMembersAlert = screen.getByRole('alert') + const noMembersAlert = screen.getByTestId('no-team-member-alert') const noMembersMsg = within(noMembersAlert).getByText('No team members') expect(noMembersMsg).toBeInTheDocument() }) diff --git a/frontend/components/projects/edit/team/SortableTeamMemberItem.tsx b/frontend/components/projects/edit/team/SortableTeamMemberItem.tsx index fea45cf8e..37bb68364 100644 --- a/frontend/components/projects/edit/team/SortableTeamMemberItem.tsx +++ b/frontend/components/projects/edit/team/SortableTeamMemberItem.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -13,7 +13,7 @@ import ContributorAvatar from '~/components/software/ContributorAvatar' import {combineRoleAndAffiliation, getDisplayInitials, getDisplayName} from '~/utils/getDisplayName' import SortableListItemActions from '~/components/layout/SortableListItemActions' import {TeamMember} from '~/types/Project' -import {useMediaQuery} from '@mui/material' +import useMediaQuery from '@mui/material/useMediaQuery' import {getImageUrl} from '~/utils/editImage' type TeamMemberProps = { diff --git a/frontend/components/projects/edit/team/SortableTeamMemberList.tsx b/frontend/components/projects/edit/team/SortableTeamMemberList.tsx index b58c9c582..4119e0f00 100644 --- a/frontend/components/projects/edit/team/SortableTeamMemberList.tsx +++ b/frontend/components/projects/edit/team/SortableTeamMemberList.tsx @@ -1,11 +1,14 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 -import {Alert, AlertTitle} from '@mui/material' +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' import {TeamMember} from '~/types/Project' import SortableList from '~/components/layout/SortableList' @@ -23,9 +26,11 @@ export default function SortableTeamMemberList({members, onEdit, onDelete, onSor // show message when no members if (members.length === 0) { return ( - + No team members - Add team member using search form! + Add team member using the search form! ) } diff --git a/frontend/components/projects/edit/team/TeamMemberModal.tsx b/frontend/components/projects/edit/team/TeamMemberModal.tsx index 8d42c6e62..6bbcd6a38 100644 --- a/frontend/components/projects/edit/team/TeamMemberModal.tsx +++ b/frontend/components/projects/edit/team/TeamMemberModal.tsx @@ -1,17 +1,20 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // // SPDX-License-Identifier: Apache-2.0 import {ChangeEvent, useEffect, useState} from 'react' -import { - Button, Dialog, DialogActions, DialogContent, - DialogTitle, useMediaQuery -} from '@mui/material' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' import DeleteIcon from '@mui/icons-material/Delete' + import {useForm} from 'react-hook-form' import {useSession} from '~/auth' diff --git a/frontend/components/projects/edit/team/index.tsx b/frontend/components/projects/edit/team/index.tsx index 1c4018cb2..ff3e690da 100644 --- a/frontend/components/projects/edit/team/index.tsx +++ b/frontend/components/projects/edit/team/index.tsx @@ -2,7 +2,9 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -25,6 +27,7 @@ import TeamMemberModal from './TeamMemberModal' import useTeamMembers from './useTeamMembers' import SortableTeamMemberList from './SortableTeamMemberList' import {deleteImage} from '~/utils/editImage' +import ContributorPrivacyHint from '~/components/layout/ContributorPrivacyHint' type EditMemberModal = ModalProps & { member?: TeamMember @@ -201,6 +204,7 @@ export default function ProjectTeam({slug}: { slug: string }) { title={cfgTeamMembers.find.title} subtitle={cfgTeamMembers.find.subtitle} /> +

DOI:

- - {doi} - + + + {doi} + + +
diff --git a/frontend/components/software/CitationDownload.tsx b/frontend/components/software/CitationDownload.tsx index 1fff51498..d3c5d4600 100644 --- a/frontend/components/software/CitationDownload.tsx +++ b/frontend/components/software/CitationDownload.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -10,59 +10,40 @@ import Button from '@mui/material/Button' import DownloadIcon from '@mui/icons-material/Download' import CiteDropdown from './CiteDropdown' -import {SoftwareCitationContent} from '../../types/SoftwareCitation' -import {citationFormats, CitationFormatType} from './citationFormats' +import {citationOptions} from './citationFormats' -function getAvailableFormats(citation:SoftwareCitationContent){ - const valid = citationFormats.map((item,pos)=>{ - let disabled=false - if (citation.hasOwnProperty(item.format)===false){ - disabled=true - } - return { - ...item, - disabled, - value: pos.toString() - } - }) - return valid +function getAvailableOptions(){ + const values = Object.keys(citationOptions) + const options = values.map(key => ({ + label: citationOptions[key].label, + value: key + })) + + return options } -export default function CitationFormat({citation}: { citation: SoftwareCitationContent }) { +export default function CitationDownload({doi}: {doi:string}) { const router = useRouter() - const [format,setFormat]=useState({v:'',f:'',e:'',t:'',n:''}) - const options = getAvailableFormats(citation) + const options = getAvailableOptions() + const [selected, setSelected] = useState() - function getFileName(option: CitationFormatType) { - const {slug} = router.query - switch (option.format) { - case 'bibtex': - case 'endnote': - case 'ris': - return `${slug}.${option.ext}` - case 'codemeta': - return 'codemeta.json' - case 'cff': - return 'CITATION.cff' - default: - return `${option.format}.${option.ext}` + function getFileName(format: string) { + const download = citationOptions[format] + let baseName = format + if (router.query['slug']) { + baseName = router.query['slug'].toString() } + return `${baseName}.${download.ext}` } - function onFormatChange({target}:{target:SelectChangeEvent['target']}){ + function onFormatChange({target}: { target: SelectChangeEvent['target'] }) { if (target?.value) { - setFormat({ - v: target?.value, - f: options[parseInt(target.value)].format, - t: options[parseInt(target.value)].contentType, - e: options[parseInt(target.value)].ext, - n: getFileName(options[parseInt(target.value)]) - }) + setSelected(target.value) } } function renderDownloadBtn(){ - if (format.v === ''){ + if (!selected){ // return disabled button return( ) } + const fileName = getFileName(selected) // return download link return ( ) } @@ -103,7 +86,7 @@ export default function CitationFormat({citation}: { citation: SoftwareCitationC {renderDownloadBtn()} diff --git a/frontend/components/software/CitationSection.test.tsx b/frontend/components/software/CitationSection.test.tsx index 582b4ce4d..d7441a93f 100644 --- a/frontend/components/software/CitationSection.test.tsx +++ b/frontend/components/software/CitationSection.test.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -35,7 +35,7 @@ describe('with dummy data',()=>{ it('should render first release doi',()=>{ // get doi from dummy data - const firstDoi = citationInfo.release_content[0].doi + const firstDoi = citationInfo[0].release_doi // find it const doi = screen.getByText(firstDoi) // assert @@ -49,19 +49,19 @@ describe('with dummy data',()=>{ expect(dropwdowns.length).toEqual(2) }) - it('should have "Copy to clipboard" button',()=>{ + it('should have "Copy DOI" button',()=>{ // find it const copyButton = screen.getByRole('button',{ - name:'Copy to clipboard' + name:'Copy DOI' }) // assert expect(copyButton).toBeInTheDocument() }) - it('should have "Download file" button initialy disabled',()=>{ + it('should have "Download citation" button initialy disabled',()=>{ // find it const downloadButton = screen.getByRole('button',{ - name:'Download file' + name:'Download citation' }) // assert // it exists diff --git a/frontend/components/software/CitationSection.tsx b/frontend/components/software/CitationSection.tsx index 795a95232..c292707b4 100644 --- a/frontend/components/software/CitationSection.tsx +++ b/frontend/components/software/CitationSection.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -11,39 +11,46 @@ import PageContainer from '../layout/PageContainer' import CiteDropdown from './CiteDropdown' import CitationDoi from './CitationDoi' import CitationDownload from './CitationDownload' -import {SoftwareCitationInfo,SoftwareCitationContent} from '../../types/SoftwareCitation' +// import {SoftwareCitationInfo,SoftwareCitationContent} from '../../types/SoftwareCitation' +import {SoftwareReleaseInfo} from '../organisation/releases/useSoftwareReleases' export default function CitationSection({citationInfo,concept_doi}: - {citationInfo:SoftwareCitationInfo, concept_doi:string|null}) { + {citationInfo:SoftwareReleaseInfo[]|null, concept_doi:string|null}) { const [version, setVersion]=useState('') - const [citation, setCitation] = useState() + const [citation, setCitation] = useState() useEffect(()=>{ // select first option by default - if (citationInfo?.release_content?.length > 0){ + if (citationInfo && citationInfo.length > 0){ setVersion('0') - setCitation(citationInfo?.release_content[0]) + setCitation(citationInfo[0]) } },[citationInfo]) - // do not render section if no data or not citable - if (!citationInfo || citationInfo.is_citable === false) { + // do not render section if no release data + if (typeof citationInfo==='undefined' || citationInfo===null || citationInfo.length===0) { // only return spacer return (
) } // prepare release versions - const versions = citationInfo?.release_content?.map((item,pos)=>{ - return {label:item.tag,value:`${pos}`} + const versions = citationInfo?.map((item, pos) => { + if (item?.release_tag) { + return {label:item?.release_tag,value:`${pos}`} + } else { + return {label: item.release_doi,value:`${pos}`} + } }) function onVersionChange({target}:{target:SelectChangeEvent['target']}){ const pos = parseInt(target?.value) - const cite = citationInfo?.release_content[pos] - // update local state - setVersion(target?.value) - setCitation(cite) + if (citationInfo) { + const cite = citationInfo[pos] + // update local state + setVersion(target?.value) + setCitation(cite) + } } // render section @@ -59,7 +66,7 @@ export default function CitationSection({citationInfo,concept_doi}: { versions?.length > 0 ?
- - { // only when citability full - citation?.citability==='full' ? - - :null - } + + {/* NOTE! temporarly dissabled */} +
diff --git a/frontend/components/software/__mocks__/softwareCitationInfo.json b/frontend/components/software/__mocks__/softwareCitationInfo.json index e788c85d0..7857b25ec 100644 --- a/frontend/components/software/__mocks__/softwareCitationInfo.json +++ b/frontend/components/software/__mocks__/softwareCitationInfo.json @@ -1,40 +1,54 @@ -{ - "id": "e89b2d3d-7efc-44f3-810d-3a09a83de209", - "software": "03e46e16-fa53-4cb6-996d-4e8204c7c5af", - "is_citable": true, - "latest_schema_dot_org": "{\n \"@context\": \"https://schema.org\",\n \"@type\": \"SoftwareSourceCode\",\n \"author\": [\n {\n \"@type\": \"Person\",\n \"affiliation\": {\n \"@type\": \"Organization\",\n \"legalName\": \"Netherlands eScience Center\"\n },\n \"familyName\": \"Attema\",\n \"givenName\": \"Jisk\"\n },\n {\n \"@id\": \"https://orcid.org/0000-0002-0989-929X\",\n \"@type\": \"Person\",\n \"affiliation\": {\n \"@type\": \"Organization\",\n \"legalName\": \"Netherlands eScience Center\"\n },\n \"familyName\": \"Diblen\",\n \"givenName\": \"Faruk\"\n }\n ],\n \"codeRepository\": \"https://github.com/NLeSC/spot\",\n \"datePublished\": \"2020-10-21\",\n \"identifier\": \"https://doi.org/10.5281/zenodo.4116521\",\n \"keywords\": [\n \"visualization\",\n \"big data\",\n \"visual data analytics\",\n \"multi-dimensional data\"\n ],\n \"license\": \"http://www.apache.org/licenses/LICENSE-2.0\",\n \"name\": \"spot\",\n \"version\": \"0.2.0\"\n}", - "created_at": "2021-12-21T11:52:11.15736", - "updated_at": "2021-12-21T11:52:11.15736", - "release_content": [ - { - "id": "176988a3-28d0-4cac-b657-8a9a506c8e69", - "release_id": "e89b2d3d-7efc-44f3-810d-3a09a83de209", - "citability": "full", - "date_published": "2020-10-21", - "doi": "10.5281/zenodo.4116521", - "tag": "0.2.0", - "url": "https://github.com/NLeSC/spot/tree/0.2.0", - "bibtex": "@misc{YourReferenceHere,\nauthor = {\n Jisk Attema and\n Faruk Diblen\n },\ntitle = {spot},\nmonth = {10},\nyear = {2020},\ndoi = {10.5281/zenodo.4116521},\nurl = {https://github.com/NLeSC/spot}\n}\n", - "cff": "authors:\n- affiliation: Netherlands eScience Center\n family-names: Attema\n given-names: Jisk\n- affiliation: Netherlands eScience Center\n family-names: Diblen\n given-names: Faruk\n orcid: https://orcid.org/0000-0002-0989-929X\ncff-version: 1.0.3\ndate-released: 2020-10-21\ndoi: 10.5281/zenodo.4116521\nkeywords:\n- visualization\n- big data\n- visual data analytics\n- multi-dimensional data\nlicense: Apache-2.0\nmessage: If you use this software, please cite it using these metadata.\nrepository-code: https://github.com/NLeSC/spot\ntitle: spot\nversion: 0.2.0\n", - "codemeta": "{\n \"@context\": [\n \"https://doi.org/10.5063/schema/codemeta-2.0\",\n \"http://schema.org\"\n ],\n \"@type\": \"SoftwareSourceCode\",\n \"author\": [\n {\n \"@type\": \"Person\",\n \"affiliation\": {\n \"@type\": \"Organization\",\n \"legalName\": \"Netherlands eScience Center\"\n },\n \"familyName\": \"Attema\",\n \"givenName\": \"Jisk\"\n },\n {\n \"@id\": \"https://orcid.org/0000-0002-0989-929X\",\n \"@type\": \"Person\",\n \"affiliation\": {\n \"@type\": \"Organization\",\n \"legalName\": \"Netherlands eScience Center\"\n },\n \"familyName\": \"Diblen\",\n \"givenName\": \"Faruk\"\n }\n ],\n \"codeRepository\": \"https://github.com/NLeSC/spot\",\n \"datePublished\": \"2020-10-21\",\n \"identifier\": \"https://doi.org/10.5281/zenodo.4116521\",\n \"keywords\": [\n \"visualization\",\n \"big data\",\n \"visual data analytics\",\n \"multi-dimensional data\"\n ],\n \"license\": \"http://www.apache.org/licenses/LICENSE-2.0\",\n \"name\": \"spot\",\n \"version\": \"0.2.0\"\n}", - "endnote": "%0\n%0 Generic\n%A Attema, Jisk & Diblen, Faruk\n%D 2020\n%T spot\n%E\n%B\n%C\n%I GitHub repository\n%V\n%6\n%N\n%P\n%&\n%Y\n%S\n%7\n%8 10\n%9\n%?\n%!\n%Z\n%@\n%(\n%)\n%*\n%L\n%M\n\n\n%2\n%3\n%4\n%#\n%$\n%F YourReferenceHere\n%K \"visualization\", \"big data\", \"visual data analytics\", \"multi-dimensional data\"\n%X\n%Z\n%U https://github.com/NLeSC/spot\n", - "ris": "TY - COMP\nAU - Attema, Jisk\nAU - Diblen, Faruk\nDO - 10.5281/zenodo.4116521\nKW - visualization\nKW - big data\nKW - visual data analytics\nKW - multi-dimensional data\nM3 - software\nPB - GitHub Inc.\nPP - San Francisco, USA\nPY - 2020/10/21\nT1 - spot\nUR - https://github.com/NLeSC/spot\nER -\n", - "schema_dot_org": "{\n \"@context\": \"https://schema.org\",\n \"@type\": \"SoftwareSourceCode\",\n \"author\": [\n {\n \"@type\": \"Person\",\n \"affiliation\": {\n \"@type\": \"Organization\",\n \"legalName\": \"Netherlands eScience Center\"\n },\n \"familyName\": \"Attema\",\n \"givenName\": \"Jisk\"\n },\n {\n \"@id\": \"https://orcid.org/0000-0002-0989-929X\",\n \"@type\": \"Person\",\n \"affiliation\": {\n \"@type\": \"Organization\",\n \"legalName\": \"Netherlands eScience Center\"\n },\n \"familyName\": \"Diblen\",\n \"givenName\": \"Faruk\"\n }\n ],\n \"codeRepository\": \"https://github.com/NLeSC/spot\",\n \"datePublished\": \"2020-10-21\",\n \"identifier\": \"https://doi.org/10.5281/zenodo.4116521\",\n \"keywords\": [\n \"visualization\",\n \"big data\",\n \"visual data analytics\",\n \"multi-dimensional data\"\n ],\n \"license\": \"http://www.apache.org/licenses/LICENSE-2.0\",\n \"name\": \"spot\",\n \"version\": \"0.2.0\"\n}" - }, - { - "id": "17611732-eba0-46c8-ac05-15c677451f33", - "release_id": "e89b2d3d-7efc-44f3-810d-3a09a83de209", - "citability": "doi-only", - "date_published": "2017-10-06", - "doi": "10.5281/zenodo.1003346", - "tag": "0.1.0", - "url": "https://github.com/NLeSC/spot/tree/0.1.0", - "bibtex": null, - "cff": null, - "codemeta": null, - "endnote": null, - "ris": null, - "schema_dot_org": null - } - ] -} \ No newline at end of file +[ + { + "software_id": "3096065c-6d1d-4042-a6ca-734673b5c2cb", + "software_slug": "software-initiatives-west-brooks-refined-interfaces-bluetooth-female-circle-synthesize-woot-hacking-southwest-microchip-computer-concrete-buckinghamshire-payment-carelessly-synergized-south-cambridge", + "software_name": "Software: initiatives West Brooks Refined interfaces bluetooth female Circle synthesize woot hacking Southwest microchip Computer Concrete Buckinghamshire payment carelessly Synergized South Cambridge", + "release_doi": "10.5281/zenodo.3258170", + "release_tag": "v0.3.5", + "release_date": "2019-06-27", + "release_year": 2019, + "release_authors": "Stefan Verhoeven, George Kanev, Albert J. Kooistra, Ross McGuire, Chris De Graaf", + "organisation_slug": [ + "organisation-southeast-investor-collaboration-male-developer-rufiyaa-yesenia-synchronised-northeast-non-facilitator-chair-deposit-customizable-convergence-yahoo-termination-naira-est-compressing-conn" + ] + }, + { + "software_id": "3096065c-6d1d-4042-a6ca-734673b5c2cb", + "software_slug": "software-initiatives-west-brooks-refined-interfaces-bluetooth-female-circle-synthesize-woot-hacking-southwest-microchip-computer-concrete-buckinghamshire-payment-carelessly-synergized-south-cambridge", + "software_name": "Software: initiatives West Brooks Refined interfaces bluetooth female Circle synthesize woot hacking Southwest microchip Computer Concrete Buckinghamshire payment carelessly Synergized South Cambridge", + "release_doi": "10.5281/zenodo.1168400", + "release_tag": "v0.3.4", + "release_date": "2018-02-07", + "release_year": 2018, + "release_authors": "Stefan Verhoeven, George Kanev, Albert J. Kooistra, Ross McGuire, Chris De Graaf", + "organisation_slug": [ + "organisation-southeast-investor-collaboration-male-developer-rufiyaa-yesenia-synchronised-northeast-non-facilitator-chair-deposit-customizable-convergence-yahoo-termination-naira-est-compressing-conn" + ] + }, + { + "software_id": "3096065c-6d1d-4042-a6ca-734673b5c2cb", + "software_slug": "software-initiatives-west-brooks-refined-interfaces-bluetooth-female-circle-synthesize-woot-hacking-southwest-microchip-computer-concrete-buckinghamshire-payment-carelessly-synergized-south-cambridge", + "software_name": "Software: initiatives West Brooks Refined interfaces bluetooth female Circle synthesize woot hacking Southwest microchip Computer Concrete Buckinghamshire payment carelessly Synergized South Cambridge", + "release_doi": "10.5281/zenodo.1043813", + "release_tag": "v0.3.3", + "release_date": "2017-11-08", + "release_year": 2017, + "release_authors": "Stefan Verhoeven, George Kanev, Albert J. Kooistra, Ross McGuire, Chris De Graaf", + "organisation_slug": [ + "organisation-southeast-investor-collaboration-male-developer-rufiyaa-yesenia-synchronised-northeast-non-facilitator-chair-deposit-customizable-convergence-yahoo-termination-naira-est-compressing-conn" + ] + }, + { + "software_id": "3096065c-6d1d-4042-a6ca-734673b5c2cb", + "software_slug": "software-initiatives-west-brooks-refined-interfaces-bluetooth-female-circle-synthesize-woot-hacking-southwest-microchip-computer-concrete-buckinghamshire-payment-carelessly-synergized-south-cambridge", + "software_name": "Software: initiatives West Brooks Refined interfaces bluetooth female Circle synthesize woot hacking Southwest microchip Computer Concrete Buckinghamshire payment carelessly Synergized South Cambridge", + "release_doi": "10.5281/zenodo.997273", + "release_tag": "v0.3.2", + "release_date": "2017-09-26", + "release_year": 2017, + "release_authors": "Stefan Verhoeven, George Kanev, Albert J. Kooistra, Ross McGuire, Chris De Graaf", + "organisation_slug": [ + "organisation-southeast-investor-collaboration-male-developer-rufiyaa-yesenia-synchronised-northeast-non-facilitator-chair-deposit-customizable-convergence-yahoo-termination-naira-est-compressing-conn" + ] + } +] \ No newline at end of file diff --git a/frontend/components/software/__mocks__/softwareCitationInfo.json.license b/frontend/components/software/__mocks__/softwareCitationInfo.json.license index 5adf4eefb..b6c19ca45 100644 --- a/frontend/components/software/__mocks__/softwareCitationInfo.json.license +++ b/frontend/components/software/__mocks__/softwareCitationInfo.json.license @@ -1,5 +1,5 @@ -SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -SPDX-FileCopyrightText: 2022 dv4all +SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +SPDX-FileCopyrightText: 2022 - 2023 dv4all SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: CC-BY-4.0 diff --git a/frontend/components/software/citationFormats.ts b/frontend/components/software/citationFormats.ts index d2da0bdb1..726596f35 100644 --- a/frontend/components/software/citationFormats.ts +++ b/frontend/components/software/citationFormats.ts @@ -1,18 +1,57 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 /** * Citation file formats supported by RSD - * These items are used by */ -export const citationFormats = [ - {label:'BibTex',format:'bibtex',contentType:'application/x-bibtex',ext:'bib'}, - {label:'EndNote',format:'endnote',contentType:'text/plain',ext:'enw'}, - {label:'RIS',format:'ris',contentType:'application/x-research-info-systems',ext:'ris'}, - {label:'CodeMeta',format:'codemeta',contentType:'application/json',ext:'json'}, - {label:'Citation File Format',format:'cff',contentType:'text/yaml',ext:'cff'} -] -export type CitationFormatType = typeof citationFormats[0] +// type DownloadFormat = 'bibtex' | 'codemeta' | 'jats' | 'ris' | 'apa' | 'mla' + +export type CitationDownloadFormat = { + [key: string]: { + label: string + contentType: string + ext: string + } +} + +export const citationOptions: CitationDownloadFormat = { + bibtex: { + label: 'BibTex', + contentType: 'application/x-bibtex', + ext: 'bib' + }, + codemeta: { + label: 'CodeMeta', + contentType: 'application/vnd.codemeta.ld+json', + ext: 'codemeta.json' + }, + citeproc: { + label: 'CSL-JSON', + contentType: 'application/vnd.citationstyles.csl+json', + ext: 'csl.json' + }, + apa: { + label: 'CSL-APA', + contentType: 'text/x-bibliography;style=apa', + ext: 'apa.txt' + }, + mla: { + label: 'CSL-MLA', + contentType: 'text/x-bibliography;style=mla', + ext: 'mla.txt' + }, + jats: { + label: 'JATS', + contentType: 'application/vnd.jats+xml', + ext: 'jats.xml' + }, + ris: { + label: 'RIS', + contentType: 'application/x-research-info-systems', + ext: 'ris' + }, +} + diff --git a/frontend/components/software/edit/contributors/EditContributorModal.tsx b/frontend/components/software/edit/contributors/EditContributorModal.tsx index 46e36279e..b789a96b5 100644 --- a/frontend/components/software/edit/contributors/EditContributorModal.tsx +++ b/frontend/components/software/edit/contributors/EditContributorModal.tsx @@ -1,17 +1,20 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // // SPDX-License-Identifier: Apache-2.0 -import {ChangeEvent, useEffect, useState} from 'react' -import { - Button, Dialog, DialogActions, DialogContent, - DialogTitle, useMediaQuery -} from '@mui/material' +import {ChangeEvent, useState} from 'react' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' import DeleteIcon from '@mui/icons-material/Delete' + import {useForm} from 'react-hook-form' import {useSession} from '~/auth' diff --git a/frontend/components/software/edit/contributors/EditSoftwareContributorsIndex.test.tsx b/frontend/components/software/edit/contributors/EditSoftwareContributorsIndex.test.tsx index 7677f7a1d..e3d523fd3 100644 --- a/frontend/components/software/edit/contributors/EditSoftwareContributorsIndex.test.tsx +++ b/frontend/components/software/edit/contributors/EditSoftwareContributorsIndex.test.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 dv4all // @@ -101,7 +102,7 @@ describe('frontend/components/software/edit/contributors/index.tsx', () => { await waitForElementToBeRemoved(screen.getByRole('progressbar')) // shows no members alert message - const noContributorsAlert = screen.getByRole('alert') + const noContributorsAlert = screen.getByTestId('no-contributor-alert') const noContributorsMsg = within(noContributorsAlert).getByText('No contributors') expect(noContributorsMsg).toBeInTheDocument() }) diff --git a/frontend/components/software/edit/contributors/GetContributorsFromDoi.tsx b/frontend/components/software/edit/contributors/GetContributorsFromDoi.tsx index 07498e348..37414b93e 100644 --- a/frontend/components/software/edit/contributors/GetContributorsFromDoi.tsx +++ b/frontend/components/software/edit/contributors/GetContributorsFromDoi.tsx @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 +import CircularProgress from '@mui/material/CircularProgress' import Button from '@mui/material/Button' import DownloadIcon from '@mui/icons-material/Download' @@ -16,7 +17,6 @@ import {postContributor} from '~/utils/editContributors' import {useSession} from '~/auth' import useSnackbar from '~/components/snackbar/useSnackbar' import {getDisplayName} from '~/utils/getDisplayName' -import {CircularProgress} from '@mui/material' import {getPropsFromObject} from '~/utils/getPropsFromObject' type GetContributorsFromDoiProps = { diff --git a/frontend/components/software/edit/contributors/SortableContributorItem.tsx b/frontend/components/software/edit/contributors/SortableContributorItem.tsx index 486befb5f..002ac8ea9 100644 --- a/frontend/components/software/edit/contributors/SortableContributorItem.tsx +++ b/frontend/components/software/edit/contributors/SortableContributorItem.tsx @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 +import useMediaQuery from '@mui/material/useMediaQuery' import ListItem from '@mui/material/ListItem' import ListItemText from '@mui/material/ListItemText' import ListItemAvatar from '@mui/material/ListItemAvatar' @@ -13,7 +14,7 @@ import ContributorAvatar from '~/components/software/ContributorAvatar' import {combineRoleAndAffiliation, getDisplayInitials, getDisplayName} from '~/utils/getDisplayName' import SortableListItemActions from '~/components/layout/SortableListItemActions' import {Contributor} from '~/types/Contributor' -import {useMediaQuery} from '@mui/material' + import {getImageUrl} from '~/utils/editImage' type SortableContributorItemProps = { diff --git a/frontend/components/software/edit/contributors/SortableContributorsList.tsx b/frontend/components/software/edit/contributors/SortableContributorsList.tsx index fd6ce86e5..95707850a 100644 --- a/frontend/components/software/edit/contributors/SortableContributorsList.tsx +++ b/frontend/components/software/edit/contributors/SortableContributorsList.tsx @@ -1,11 +1,14 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 -import {Alert, AlertTitle} from '@mui/material' +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' -import {Contributor} from '../../../../types/Contributor' +import {Contributor} from '~/types/Contributor' import SortableList from '~/components/layout/SortableList' import SortableContributorItem from './SortableContributorItem' @@ -20,9 +23,11 @@ export default function SortableContributorsList({contributors, onEdit, onDelete if (contributors.length === 0) { return ( - + No contributors - Add contributors using search form! + Add contributors using the search form! ) } diff --git a/frontend/components/software/edit/contributors/index.tsx b/frontend/components/software/edit/contributors/index.tsx index 05c112783..597472873 100644 --- a/frontend/components/software/edit/contributors/index.tsx +++ b/frontend/components/software/edit/contributors/index.tsx @@ -1,7 +1,8 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // // SPDX-License-Identifier: Apache-2.0 @@ -29,6 +30,7 @@ import useSoftwareContext from '../useSoftwareContext' import useSoftwareContributors from './useSoftwareContributors' import SortableContributorsList from './SortableContributorsList' import {deleteImage} from '~/utils/editImage' +import ContributorPrivacyHint from '~/components/layout/ContributorPrivacyHint' type EditContributorModal = ModalProps & { contributor?: Contributor @@ -221,6 +223,7 @@ export default function SoftwareContributors() { title={config.findContributor.title} subtitle={config.findContributor.subtitle} /> + -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // // SPDX-License-Identifier: Apache-2.0 import {ChangeEvent, useEffect} from 'react' -import { - Avatar, - Button, Dialog, DialogActions, DialogContent, - DialogTitle, useMediaQuery -} from '@mui/material' +import Avatar from '@mui/material/Avatar' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' import DeleteIcon from '@mui/icons-material/Delete' import {useForm} from 'react-hook-form' diff --git a/frontend/components/software/edit/organisations/FindOrganisationItem.tsx b/frontend/components/software/edit/organisations/FindOrganisationItem.tsx index 62f3a9607..f5dd4f7a1 100644 --- a/frontend/components/software/edit/organisations/FindOrganisationItem.tsx +++ b/frontend/components/software/edit/organisations/FindOrganisationItem.tsx @@ -1,5 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -24,7 +26,7 @@ export default function FindOrganisationItem({option}: { option: AutocompleteOpt } return ( -
+
void } - export default function SortableOrganisationsList({organisations,onEdit,onDelete,onSorted}:OrganisationListProps) { if (organisations.length === 0) { diff --git a/frontend/components/software/edit/testimonials/EditTestimonialModal.tsx b/frontend/components/software/edit/testimonials/EditTestimonialModal.tsx index 23e595c4e..92ecee567 100644 --- a/frontend/components/software/edit/testimonials/EditTestimonialModal.tsx +++ b/frontend/components/software/edit/testimonials/EditTestimonialModal.tsx @@ -1,16 +1,18 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 import {useEffect} from 'react' -import { - Button, Dialog, DialogActions, DialogContent, - DialogTitle, useMediaQuery -} from '@mui/material' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' import {useForm} from 'react-hook-form' import ControlledTextField from '../../../form/ControlledTextField' diff --git a/frontend/components/user/UserNavItems.tsx b/frontend/components/user/UserNavItems.tsx index 0dc454c35..6cc51c0aa 100644 --- a/frontend/components/user/UserNavItems.tsx +++ b/frontend/components/user/UserNavItems.tsx @@ -1,16 +1,18 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 import TerminalIcon from '@mui/icons-material/Terminal' import ListAltIcon from '@mui/icons-material/ListAlt' -import PersonIcon from '@mui/icons-material/Person' import BusinessIcon from '@mui/icons-material/Business' +import SettingsIcon from '@mui/icons-material/Settings' -import UserProfile from './profile' +import UserSettings from './settings' import UserSoftware from './software' import UserProjects from './project' import Organisations from './organisations' @@ -29,14 +31,6 @@ export type UserMenuItems = { } export const userMenu:UserMenuItems = { - profile:{ - id:'profile', - label:()=>'Profile', - icon: , - component: (props?) => , - status: 'User profile info', - showSearch: false - }, software:{ id:'software', label:({software_cnt})=>`Software (${software_cnt ?? 0})`, @@ -61,4 +55,12 @@ export const userMenu:UserMenuItems = { status: 'Departments or institutions you maintain', showSearch: true }, + settings:{ + id:'settings', + label: () => 'Settings', + icon: , + component: (props?) => , + status: 'Your profile settings', + showSearch: false + } } diff --git a/frontend/components/user/profile/index.tsx b/frontend/components/user/profile/index.tsx deleted file mode 100644 index 2fb0b1084..000000000 --- a/frontend/components/user/profile/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) -// -// SPDX-License-Identifier: Apache-2.0 - -import {Session} from '~/auth' - -export default function UserProfile({session}: { session: Session }) { - - return ( -
- {/*

Your profile

*/} -
-
Account id
- {session?.user?.account ?? ''} -
-
-
Name
- {session?.user?.name ?? ''} -
-
-
Role
- {session?.user?.role ?? ''} -
- {/*
-        {JSON.stringify(session,null,2)}
-      
*/} -
- ) -} diff --git a/frontend/components/user/settings/UserAgreementForm.tsx b/frontend/components/user/settings/UserAgreementForm.tsx new file mode 100644 index 000000000..b8faa40e8 --- /dev/null +++ b/frontend/components/user/settings/UserAgreementForm.tsx @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import useRsdSettings from '~/config/useRsdSettings' +import Link from 'next/link' +import ControlledSwitch from '~/components/form/ControlledSwitch' +import {useFormContext} from 'react-hook-form' +import useSnackbar from '~/components/snackbar/useSnackbar' +import {useSession} from '~/auth' +import {patchAccountTable} from './patchAccountTable' + +type UserAgreementFormProps = { + agreeTerms: boolean, + noticePrivacyStatement: boolean, + showTitle?: boolean, + setAgreeTerms: any, + setNoticePrivacy: any +} + +export default function UserAgreementForm({agreeTerms, noticePrivacyStatement, showTitle=true, setAgreeTerms, setNoticePrivacy}: UserAgreementFormProps) { + const {host} = useRsdSettings() + const {control} = useFormContext() + const {showErrorMessage} = useSnackbar() + const {token,user} = useSession() + + async function saveTermsAgreement(value: boolean) { + const resp = await patchAccountTable({ + account: user?.account ?? '', + data: { + // name of the colum and the value + ['agree_terms']: value + }, + token + }) + + if (resp?.status === 200) { + setAgreeTerms(value) + } else { + showErrorMessage(`Failed to save agreement. ${resp?.message}`) + } + } + + async function saveNoticePrivacyStatement(value: boolean) { + const resp = await patchAccountTable({ + account: user?.account ?? '', + data: { + // name of the colum and the value + ['notice_privacy_statement']: value + }, + token + }) + + if (resp?.status === 200) { + setNoticePrivacy(value) + } else { + showErrorMessage(`Failed to save agreement. ${resp?.message}`) + } + } + + + return( + +
+ {showTitle && +

User agreements

+ } +
To be able to contribute to the RSD, we need to know that you agree to our Terms of Service, and that you have read the Privacy Statement. Please check all of the points below to proceed:
+
+ + I agree to the Terms of Service. +
+
+ + I have read the Privacy Statement. +
+
+ + ) +} diff --git a/frontend/components/user/settings/UserAgreementModal.test.tsx b/frontend/components/user/settings/UserAgreementModal.test.tsx new file mode 100644 index 000000000..49c7fb4fe --- /dev/null +++ b/frontend/components/user/settings/UserAgreementModal.test.tsx @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {fireEvent, render, screen, waitFor} from '@testing-library/react' +import {WithAppContext, mockSession} from '~/utils/jest/WithAppContext' + +import UserAgrementModal from './UserAgreementModal' + +// MOCK fetchAgreementStatus +const mockFetchAgreementStatus = jest.fn() +jest.mock('./fetchAgreementStatus', () => ({ + fetchAgreementStatus:jest.fn(props=>mockFetchAgreementStatus(props)) +})) + +// MOCK patchAccountTable +const mockPatchAccountTable = jest.fn() +jest.mock('./patchAccountTable', () => ({ + patchAccountTable:jest.fn(props=>mockPatchAccountTable(props)) +})) + +beforeEach(() => { + jest.clearAllMocks() +}) + +it('renders modal when agree_terms=false', async() => { + + mockFetchAgreementStatus.mockResolvedValueOnce({ + status: 200, + data: { + agree_terms: false, + notice_privacy_statement: true + } + }) + + render( + + + + ) + + const modal = await screen.findByTestId('user-agreement-modal') +}) + +it('renders modal when notice_privacy_statement=false', async() => { + + mockFetchAgreementStatus.mockResolvedValueOnce({ + status: 200, + data: { + agree_terms: true, + notice_privacy_statement: false + } + }) + + render( + + + + ) + + const modal = await screen.findByTestId('user-agreement-modal') +}) + +it('does not render modal when terms accepted', async() => { + + mockFetchAgreementStatus.mockResolvedValueOnce({ + status: 200, + data: { + agree_terms: true, + notice_privacy_statement: true + } + }) + + render( + + + + ) + + const modal = await screen.queryByTestId('user-agreement-modal') + expect(modal).toBe(null) +}) + +it('does not render modal when role rsd_admin', async () => { + // role is rsd_admin + if (mockSession.user && mockSession.user.role) { + mockSession.user.role = 'rsd_admin' + } + + mockFetchAgreementStatus.mockResolvedValueOnce({ + status: 200, + data: { + agree_terms: false, + notice_privacy_statement: false + } + }) + + render( + + + + ) + + const modal = await screen.queryByTestId('user-agreement-modal') + expect(modal).toBe(null) +}) + +it('accepts TOS via modal and calls patch account', async() => { + // role is rsd_admin + if (mockSession.user && mockSession.user.role) { + mockSession.user.role = 'rsd_user' + } + + mockFetchAgreementStatus.mockResolvedValueOnce({ + status: 200, + data: { + agree_terms: false, + notice_privacy_statement: true + } + }) + + render( + + + + ) + + const modal = await screen.findByTestId('user-agreement-modal') + + const switches = screen.getAllByRole('checkbox') + + // first switch is agree_terms value we set to false + const tosSwitch = switches[0] + expect(tosSwitch).not.toBeChecked() + + // now we check that value + fireEvent.click(tosSwitch) + // validate checked + expect(tosSwitch).toBeChecked() + // loose focus + fireEvent.blur(tosSwitch) + + await waitFor(() => { + expect(mockPatchAccountTable).toBeCalledTimes(1) + expect(mockPatchAccountTable).toBeCalledWith({ + 'account': mockSession.user?.account, + 'data': { + 'agree_terms': true, + }, + 'token': mockSession.token + }) + }) + +}) diff --git a/frontend/components/user/settings/UserAgreementModal.tsx b/frontend/components/user/settings/UserAgreementModal.tsx new file mode 100644 index 000000000..be77f20ff --- /dev/null +++ b/frontend/components/user/settings/UserAgreementModal.tsx @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +// +// SPDX-License-Identifier: Apache-2.0 + +import {useState} from 'react' +import {useRouter} from 'next/router' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogTitle from '@mui/material/DialogTitle' +import useMediaQuery from '@mui/material/useMediaQuery' +import CheckIcon from '@mui/icons-material/Check' +import InfoIcon from '@mui/icons-material/Info' + +import {useSession} from '~/auth' +import UserAgreementForm from './UserAgreementForm' +import {FormProvider, useForm} from 'react-hook-form' +import {useGetUserAgreementStatus} from './useGetUserAgreementStatus' +import {UserSettingsType} from '~/types/SoftwareTypes' +import useSnackbar from '~/components/snackbar/useSnackbar' +import {patchAccountTable} from './patchAccountTable' + +export default function UserAgrementModal() { + const {token,user} = useSession() + const smallScreen = useMediaQuery('(max-width:600px)') + const router = useRouter() + const {showErrorMessage} = useSnackbar() + + const [open, setOpen] = useState(false) + const [agreeTerms, setAgreeTerms] = useState(false) + const [noticePrivacy, setNoticePrivacy] = useState(false) + + const userInfo = useGetUserAgreementStatus(token, user, setAgreeTerms, setNoticePrivacy, setOpen) + + const methods = useForm({ + mode: 'onChange' + }) + + function onCancel() { + // Reset the values to their initival values if the user wants to cancel + patchAccountTable({ + account: user?.account ?? '', + data: {...userInfo}, + token + }).then((resp) => { + if (resp?.status === 200) { + router.back() + } + }, (reason) => { + showErrorMessage(`Failed to restore agreements. ${reason?.message}`) + }) + } + + function onClose(event?:any,reason?:'backdropClick' | 'escapeKeyDown') { + if (typeof reason==='undefined') { + // we do not close modal on backdrop click or escape key + // only when user clicks on Cancel button the reason is undefined + setOpen(false) + } + } + + return ( + + + User agreement + + + + {/* Render only if userInfo present in order to properly load defaultValues */} + {userInfo && + + + + } +

You may view or modify your agreement at any time in your profile settings.

+
+ + + + + + +
+ ) +} diff --git a/frontend/components/user/settings/__mocks__/fetchAgreementStatus.ts b/frontend/components/user/settings/__mocks__/fetchAgreementStatus.ts new file mode 100644 index 000000000..f0c76f4c9 --- /dev/null +++ b/frontend/components/user/settings/__mocks__/fetchAgreementStatus.ts @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +// DEFAULT MOCK with terms accepted +export async function fetchAgreementStatus(token: string, account: string) { + return Promise.resolve({ + status:200, + data: { + agree_terms: true, + notice_privacy_statement: true + } + }) +} diff --git a/frontend/components/user/settings/fetchAgreementStatus.ts b/frontend/components/user/settings/fetchAgreementStatus.ts new file mode 100644 index 000000000..00b4dbf81 --- /dev/null +++ b/frontend/components/user/settings/fetchAgreementStatus.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {UserSettingsType} from '~/types/SoftwareTypes' +import {createJsonHeaders} from '~/utils/fetchHelpers' +import logger from '~/utils/logger' + +export async function fetchAgreementStatus(token: string, account: string) { + const url = `/api/v1/account?id=eq.${account}&select=agree_terms,notice_privacy_statement` + try { + const resp = await fetch(url, {headers: {...createJsonHeaders(token)}}) + const json: UserSettingsType[] = await resp.json() + return { + status: 200, + data: json[0] + } + } catch (e: any) { + logger(`Retrieving user agreement status failed: ${e.message}`, 'error') + return { + status: 500, + message: e.message + } + } +} diff --git a/frontend/components/user/settings/index.tsx b/frontend/components/user/settings/index.tsx new file mode 100644 index 000000000..c859294db --- /dev/null +++ b/frontend/components/user/settings/index.tsx @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +// +// SPDX-License-Identifier: Apache-2.0 + +import {useState} from 'react' +import {FormProvider, useForm} from 'react-hook-form' +import {useSession} from '~/auth' +import {UserSettingsType} from '~/types/SoftwareTypes' +import {useGetUserAgreementStatus} from './useGetUserAgreementStatus' +import UserAgreementForm from './UserAgreementForm' + +export default function UserSettings() { + const {token,user} = useSession() + + const [agreeTerms, setAgreeTerms] = useState(false) + const [noticePrivacy, setNoticePrivacy] = useState(false) + + const userInfo = useGetUserAgreementStatus(token, user, setAgreeTerms, setNoticePrivacy) + + const methods = useForm({ + mode: 'onChange', + }) + + return ( +
+

Your profile properties

+
+
Account id
+ {user?.account ?? ''} +
+
+
Name
+ {user?.name ?? ''} +
+
+
Role
+ {user?.role ?? ''} +
+ {/* Render only if userInfo present in order to properly load defaultValues */} + {userInfo && + + + + } +
+ ) +} diff --git a/frontend/components/user/settings/patchAccountTable.ts b/frontend/components/user/settings/patchAccountTable.ts new file mode 100644 index 000000000..bb5bcb639 --- /dev/null +++ b/frontend/components/user/settings/patchAccountTable.ts @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {createJsonHeaders, extractReturnMessage} from '~/utils/fetchHelpers' +import logger from '~/utils/logger' + +export type PatchAccountTableProps = { + account: string, + data: { + [key: string]: any + }, + token: string +} + +export async function patchAccountTable({account, data, token}: PatchAccountTableProps) { + try { + const url = `/api/v1/account?id=eq.${account}` + const resp = await fetch(url, { + method: 'PATCH', + headers: { + ...createJsonHeaders(token) + }, + body: JSON.stringify(data) + }) + return extractReturnMessage(resp, account) + } catch (e: any) { + logger(`patchAccountTable failed ${e.message}`, 'error') + return { + status: 500, + message: e.message + } + } +} diff --git a/frontend/components/user/settings/useGetUserAgreementStatus.tsx b/frontend/components/user/settings/useGetUserAgreementStatus.tsx new file mode 100644 index 000000000..53a33ae20 --- /dev/null +++ b/frontend/components/user/settings/useGetUserAgreementStatus.tsx @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +// SPDX-FileCopyrightText: 2023 dv4all +// +// SPDX-License-Identifier: Apache-2.0 + +import {useEffect, useState} from 'react' +import {RsdUser} from '~/auth' +import {UserSettingsType} from '~/types/SoftwareTypes' +import {fetchAgreementStatus} from './fetchAgreementStatus' + +export function useGetUserAgreementStatus(token: string, user:RsdUser|null, setAgreeTerms?: any, setNoticePrivacy?: any, setOpen?: any) { + const [userInfo, setUserInfo] = useState() + + useEffect(() => { + async function getUser() { + const respData = await fetchAgreementStatus(token, user?.account ?? '') + if (respData.status === 200 && typeof(respData.data) !== 'undefined') { + setUserInfo(respData.data) + if (typeof (setOpen) === 'function' && + (respData.data.agree_terms === false || respData.data.notice_privacy_statement === false) + // rsd_admin does not need to accept UA + && user?.role !== 'rsd_admin') { + setOpen(true) + } + if ( + typeof(setAgreeTerms) === 'function' && typeof(setNoticePrivacy) === 'function' && + typeof(respData.data.agree_terms) === 'boolean' && typeof(respData.data.notice_privacy_statement) === 'boolean' + ) { + setAgreeTerms(respData.data.agree_terms) + setNoticePrivacy(respData.data.notice_privacy_statement) + } + } + } + if (token && user && user?.account) { + getUser() + } + }, [token, user, setAgreeTerms, setNoticePrivacy, setOpen]) + return userInfo +} diff --git a/frontend/config/rsdSettingsReducer.ts b/frontend/config/rsdSettingsReducer.ts index 9abb31e53..f5a246345 100644 --- a/frontend/config/rsdSettingsReducer.ts +++ b/frontend/config/rsdSettingsReducer.ts @@ -1,5 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -25,7 +27,9 @@ export type RsdHost = { url: string, issues_page_url: string }, - login_info_url?:string + login_info_url?:string, + terms_of_service_url?: string, + privacy_statement_url?: string } export type CustomLink = { diff --git a/frontend/config/userMenuItems.tsx b/frontend/config/userMenuItems.tsx index 5a11417b1..dfd3c8051 100644 --- a/frontend/config/userMenuItems.tsx +++ b/frontend/config/userMenuItems.tsx @@ -1,7 +1,9 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -10,6 +12,7 @@ import ListAltIcon from '@mui/icons-material/ListAlt' import BusinessIcon from '@mui/icons-material/Business' import ManageAccountsIcon from '@mui/icons-material/ManageAccounts' import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck' +import SettingsIcon from '@mui/icons-material/Settings' import Logout from '@mui/icons-material/Logout' import {MenuItemType} from './menuItems' @@ -37,6 +40,16 @@ const userMenuItems: MenuItemType[] = [ role:['rsd_admin','rsd_user'], type: 'divider', label: 'divider1' + }, { + role:['rsd_admin','rsd_user'], + type: 'link', + label: 'My settings', + path: '/user/settings', + icon: + }, { + role:['rsd_admin'], + type: 'divider', + label: 'divider2' }, { role:['rsd_admin'], type: 'link', @@ -50,9 +63,9 @@ const userMenuItems: MenuItemType[] = [ path: '/admin/orcid-whitelist', icon: }, { - role:['rsd_admin'], + role:['rsd_admin','rsd_user'], type: 'divider', - label: 'divider2' + label: 'divider3' }, { role:['rsd_admin','rsd_user'], label: 'Logout', diff --git a/frontend/pages/admin/orcid-whitelist.tsx b/frontend/pages/admin/orcid-whitelist.tsx index 4aba27551..a2a435fc1 100644 --- a/frontend/pages/admin/orcid-whitelist.tsx +++ b/frontend/pages/admin/orcid-whitelist.tsx @@ -1,24 +1,29 @@ // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 +import {useEffect, useState} from 'react' import Head from 'next/head' - -import {app} from '../../config/app' -import RsdAdminContent from '~/auth/RsdAdminContent' -import DefaultLayout from '~/components/layout/DefaultLayout' +import Link from '@mui/material/Link' +import Button from '@mui/material/Button' +import CircularProgress from '@mui/material/CircularProgress' +import ListItemText from '@mui/material/ListItemText' +import TextField from '@mui/material/TextField' import List from '@mui/material/List' import ListItem from '@mui/material/ListItem' import DeleteIcon from '@mui/icons-material/Delete' import IconButton from '@mui/material/IconButton' + +import {useSession} from '~/auth' +import {app} from '../../config/app' +import RsdAdminContent from '~/auth/RsdAdminContent' +import DefaultLayout from '~/components/layout/DefaultLayout' import {createJsonHeaders, extractReturnMessage} from '~/utils/fetchHelpers' import useSnackbar from '~/components/snackbar/useSnackbar' import EditSectionTitle from '~/components/layout/EditSectionTitle' -import {useSession} from '~/auth' -import {useEffect, useState} from 'react' -import Link from '@mui/material/Link' -import {Button, CircularProgress, ListItemText, TextField} from '@mui/material' import {PageTitleSticky} from '~/components/layout/PageTitle' export default function OrcidWitelistPage() { diff --git a/frontend/pages/api/fe/cite/[id].ts b/frontend/pages/api/fe/cite/index.ts similarity index 58% rename from frontend/pages/api/fe/cite/[id].ts rename to frontend/pages/api/fe/cite/index.ts index c8291f5ca..55d777e96 100644 --- a/frontend/pages/api/fe/cite/[id].ts +++ b/frontend/pages/api/fe/cite/index.ts @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -7,6 +7,7 @@ import type {NextApiRequest, NextApiResponse} from 'next' import {extractParam, Error} from '~/utils/apiHelpers' import logger from '../../../../utils/logger' +import {citationOptions} from '~/components/software/citationFormats' type Data = { name: string, @@ -23,24 +24,40 @@ export default async function handler( ){ try{ // extract query parameters - const id = extractParam(req,'id') + const doi = extractParam(req,'doi') const format = extractParam(req,'f') - const type = extractParam(req, 't') const name = extractParam(req, 'n') + // validate download format + if (citationOptions.hasOwnProperty(format) === false) { + logger(`NextApi.v1.cite: uknown format ${format}`, 'error') + // query not found + res.status(400).json({ + message: `Download format ${format} NOT SUPPORTED!` + }) + } + + const contentType = citationOptions[format].contentType + + // console.log('contentType...',contentType) + // make request to postgREST api - const url = `${process.env.POSTGREST_URL}/release_content?select=${format}&id=eq.${id}` - const resp = await fetch(url,{method:'GET'}) + const url = `https://doi.org/${doi}` + const resp = await fetch(url, { + method: 'GET', + headers: { + 'Accept': contentType + } + }) // send reponse back if (resp.status===200){ - const data:any[] = await resp.json() - const content = data[0][format] + const data:any = await resp.text() // add reponse headers res.setHeader('Content-Disposition',`attachment; filename=${name}`) - res.setHeader('Content-Type',type) + res.setHeader('Content-Type', contentType) // send content - res.status(200).send(content) + res.status(200).send(data) } else if (resp.status===404){ logger(`NextApi.v1.cite: 404 [${url}]`,'error') // query not found diff --git a/frontend/pages/login/local.tsx b/frontend/pages/login/local.tsx index 0563a6d85..8ab12fb86 100644 --- a/frontend/pages/login/local.tsx +++ b/frontend/pages/login/local.tsx @@ -1,15 +1,17 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center -// SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 -import {app} from '~/config/app' import Head from 'next/head' +import Button from '@mui/material/Button' +import TextField from '@mui/material/TextField' + +import {app} from '~/config/app' import DefaultLayout from '~/components/layout/DefaultLayout' import PageTitle from '~/components/layout/PageTitle' -import {Button, TextField} from '@mui/material' import ContentInTheMiddle from '~/components/layout/ContentInTheMiddle' export default function LoginFailed() { diff --git a/frontend/pages/projects/[slug]/edit/index.tsx b/frontend/pages/projects/[slug]/edit/index.tsx index 87b7f3b6f..00aabf844 100644 --- a/frontend/pages/projects/[slug]/edit/index.tsx +++ b/frontend/pages/projects/[slug]/edit/index.tsx @@ -1,5 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -11,6 +13,7 @@ import {app} from '../../../../config/app' import DefaultLayout from '../../../../components/layout/DefaultLayout' import {EditProjectProvider} from '~/components/projects/edit/editProjectContext' import EditProjectPage from '~/components/projects/edit/EditProjectPage' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' const pageTitle = `Edit project | ${app.title}` @@ -30,6 +33,7 @@ export default function ProjectEditPage() { {pageTitle} + {/* form provider to share isValid, isDirty states in the header */} {/* edit project context is share project info between pages */} diff --git a/frontend/pages/projects/add.tsx b/frontend/pages/projects/add.tsx index 004aa247d..3c5a0a6db 100644 --- a/frontend/pages/projects/add.tsx +++ b/frontend/pages/projects/add.tsx @@ -1,5 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -9,6 +11,7 @@ import PageContainer from '~/components/layout/PageContainer' import AppFooter from '~/components/AppFooter' import AddProjectCard from '../../components/projects/add/AddProjectCard' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' /** * Add new project. This page enables creation of project with 2 fields: @@ -20,6 +23,7 @@ export default function AddSoftware() { + diff --git a/frontend/pages/software/[slug]/edit/index.tsx b/frontend/pages/software/[slug]/edit/index.tsx index 322ef0be9..d9e421e52 100644 --- a/frontend/pages/software/[slug]/edit/index.tsx +++ b/frontend/pages/software/[slug]/edit/index.tsx @@ -1,5 +1,7 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -11,6 +13,7 @@ import DefaultLayout from '~/components/layout/DefaultLayout' import {EditSoftwareProvider} from '~/components/software/edit/editSoftwareContext' import EditSoftwarePage from '~/components/software/edit/EditSoftwarePage' import {FormProvider, useForm} from 'react-hook-form' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' const pageTitle = `Edit software | ${app.title}` @@ -31,6 +34,7 @@ export default function SoftwareEditPage() { {pageTitle} + {/* form provider to share isValid, isDirty states in the header */} diff --git a/frontend/pages/software/[slug]/index.tsx b/frontend/pages/software/[slug]/index.tsx index fa5b4927f..d4aab6d6a 100644 --- a/frontend/pages/software/[slug]/index.tsx +++ b/frontend/pages/software/[slug]/index.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // @@ -51,18 +51,18 @@ import { KeywordForSoftware, License, RepositoryInfo, SoftwareItem, SoftwareListItem } from '~/types/SoftwareTypes' -import {SoftwareCitationInfo} from '~/types/SoftwareCitation' import {Contributor} from '~/types/Contributor' import {Testimonial} from '~/types/Testimonial' import {MentionItemProps} from '~/types/Mention' import {ParticipatingOrganisationProps} from '~/types/Organisation' import {RelatedProject} from '~/types/Project' import NoContent from '~/components/layout/NoContent' +import {SoftwareReleaseInfo} from '~/components/organisation/releases/useSoftwareReleases' interface SoftwareIndexData extends ScriptProps{ slug: string software: SoftwareItem - citationInfo: SoftwareCitationInfo + citationInfo: SoftwareReleaseInfo[] keywords: KeywordForSoftware[] licenseInfo: License[] repositoryInfo: RepositoryInfo @@ -104,7 +104,7 @@ export default function SoftwareIndexPage(props:SoftwareIndexData) { if (!software?.brand_name){ return } - // console.log('SoftwareIndexPage...repositoryInfo...', repositoryInfo) + // console.log('SoftwareIndexPage...citationInfo...', citationInfo) return ( <> {/* Page Head meta tags */} diff --git a/frontend/pages/software/add.tsx b/frontend/pages/software/add.tsx index 658fcc641..c57be21c4 100644 --- a/frontend/pages/software/add.tsx +++ b/frontend/pages/software/add.tsx @@ -1,5 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -10,6 +12,7 @@ import AppFooter from '~/components/AppFooter' // import AddSoftwareModal from '../../components/software/add/AddSoftwareModal' import AddSoftwareCard from '../../components/software/add/AddSoftwareCard' +import UserAgrementModal from '~/components/user/settings/UserAgreementModal' /** * Add new software. This page is only showing a modal with 2 fields: @@ -21,6 +24,7 @@ export default function AddSoftware() { + diff --git a/frontend/public/data/settings.json b/frontend/public/data/settings.json index 7989b71b8..db721c0c9 100644 --- a/frontend/public/data/settings.json +++ b/frontend/public/data/settings.json @@ -9,7 +9,9 @@ "url": "rsd@esciencecenter.nl", "issues_page_url": "https://github.com/research-software-directory/RSD-as-a-service/issues" }, - "login_info_url":"https://research-software-directory.github.io/documentation/getting-access.html" + "login_info_url":"https://research-software-directory.github.io/documentation/getting-access.html", + "terms_of_service_url": "/page/terms-of-service/", + "privacy_statement_url": "/page/privacy-statement/" }, "links": [ { diff --git a/frontend/styles/cssVariables.ts b/frontend/styles/cssVariables.ts index 7aa976de1..ba7fa33da 100644 --- a/frontend/styles/cssVariables.ts +++ b/frontend/styles/cssVariables.ts @@ -1,9 +1,9 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 -import {Theme} from '@mui/material' +import {Theme} from '@mui/material/styles/createTheme' export type CssVariableProps = { [key:string]:string | number diff --git a/frontend/styles/global.css b/frontend/styles/global.css index 256b14957..a2e314997 100644 --- a/frontend/styles/global.css +++ b/frontend/styles/global.css @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) - * SPDX-FileCopyrightText: 2021 - 2022 dv4all + * SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) + * SPDX-FileCopyrightText: 2021 - 2023 dv4all * SPDX-FileCopyrightText: 2022 Marc Hanisch (GFZ) * SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences * @@ -21,6 +21,10 @@ Default values -------------------------------------**/ +html { + scroll-behavior: smooth; +} + body{ font-size: 1rem; /* minimal with to have logo, diff --git a/frontend/types/AutocompleteOptions.ts b/frontend/types/AutocompleteOptions.ts index e3ac52cca..cccba1fdb 100644 --- a/frontend/types/AutocompleteOptions.ts +++ b/frontend/types/AutocompleteOptions.ts @@ -1,5 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 diff --git a/frontend/types/Organisation.ts b/frontend/types/Organisation.ts index 1d4e03b88..1050a1e8f 100644 --- a/frontend/types/Organisation.ts +++ b/frontend/types/Organisation.ts @@ -1,5 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -37,6 +39,7 @@ export type Organisation = CoreOrganisationProps & { id: string | null // about page content created by maintainer description: string | null + parent_names?: string } // adding source @@ -114,6 +117,7 @@ export type OrganisationForOverview = Organisation & { software_cnt: number | null project_cnt: number | null children_cnt: number | null + release_cnt: number | null rsd_path: string } diff --git a/frontend/types/SoftwareCitation.ts b/frontend/types/SoftwareCitation.ts deleted file mode 100644 index c03808511..000000000 --- a/frontend/types/SoftwareCitation.ts +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all -// -// SPDX-License-Identifier: Apache-2.0 - -// based on rsd db extract -// const rsdCitationItemContent=[{ -// 'id': '176988a3-28d0-4cac-b657-8a9a506c8e69', -// 'release_id': 'e89b2d3d-7efc-44f3-810d-3a09a83de209', -// 'citability': 'full', -// 'date_published': '2020-10-21', -// 'doi': '10.5281/zenodo.4116521', -// 'tag': '0.2.0', -// 'url': 'https://github.com/NLeSC/spot/tree/0.2.0', -// 'bibtex': '@misc{YourReferenceHere,\nauthor = {\n Jisk Attema and\n Faruk Diblen\n },\ntitle = {spot},\nmonth = {10},\nyear = {2020},\ndoi = {10.5281/zenodo.4116521},\nurl = {https://github.com/NLeSC/spot}\n}\n', -// 'cff': 'authors:\n- affiliation: Netherlands eScience Center\n family-names: Attema\n given-names: Jisk\n- affiliation: Netherlands eScience Center\n family-names: Diblen\n given-names: Faruk\n orcid: https://orcid.org/0000-0002-0989-929X\ncff-version: 1.0.3\ndate-released: 2020-10-21\ndoi: 10.5281/zenodo.4116521\nkeywords:\n- visualization\n- big data\n- visual data analytics\n- multi-dimensional data\nlicense: Apache-2.0\nmessage: If you use this software, please cite it using these metadata.\nrepository-code: https://github.com/NLeSC/spot\ntitle: spot\nversion: 0.2.0\n', -// 'codemeta': '{\n "@context": [\n "https://doi.org/10.5063/schema/codemeta-2.0",\n "http://schema.org"\n ],\n "@type": "SoftwareSourceCode",\n "author": [\n {\n "@type": "Person",\n "affiliation": {\n "@type": "Organization",\n "legalName": "Netherlands eScience Center"\n },\n "familyName": "Attema",\n "givenName": "Jisk"\n },\n {\n "@id": "https://orcid.org/0000-0002-0989-929X",\n "@type": "Person",\n "affiliation": {\n "@type": "Organization",\n "legalName": "Netherlands eScience Center"\n },\n "familyName": "Diblen",\n "givenName": "Faruk"\n }\n ],\n "codeRepository": "https://github.com/NLeSC/spot",\n "datePublished": "2020-10-21",\n "identifier": "https://doi.org/10.5281/zenodo.4116521",\n "keywords": [\n "visualization",\n "big data",\n "visual data analytics",\n "multi-dimensional data"\n ],\n "license": "http://www.apache.org/licenses/LICENSE-2.0",\n "name": "spot",\n "version": "0.2.0"\n}', -// 'endnote': '%0\n%0 Generic\n%A Attema, Jisk & Diblen, Faruk\n%D 2020\n%T spot\n%E\n%B\n%C\n%I GitHub repository\n%V\n%6\n%N\n%P\n%&\n%Y\n%S\n%7\n%8 10\n%9\n%?\n%!\n%Z\n%@\n%(\n%)\n%*\n%L\n%M\n\n\n%2\n%3\n%4\n%#\n%$\n%F YourReferenceHere\n%K "visualization", "big data", "visual data analytics", "multi-dimensional data"\n%X\n%Z\n%U https://github.com/NLeSC/spot\n', -// 'ris': 'TY - COMP\nAU - Attema, Jisk\nAU - Diblen, Faruk\nDO - 10.5281/zenodo.4116521\nKW - visualization\nKW - big data\nKW - visual data analytics\nKW - multi-dimensional data\nM3 - software\nPB - GitHub Inc.\nPP - San Francisco, USA\nPY - 2020/10/21\nT1 - spot\nUR - https://github.com/NLeSC/spot\nER -\n', -// 'schema_dot_org': '{\n "@context": "https://schema.org",\n "@type": "SoftwareSourceCode",\n "author": [\n {\n "@type": "Person",\n "affiliation": {\n "@type": "Organization",\n "legalName": "Netherlands eScience Center"\n },\n "familyName": "Attema",\n "givenName": "Jisk"\n },\n {\n "@id": "https://orcid.org/0000-0002-0989-929X",\n "@type": "Person",\n "affiliation": {\n "@type": "Organization",\n "legalName": "Netherlands eScience Center"\n },\n "familyName": "Diblen",\n "givenName": "Faruk"\n }\n ],\n "codeRepository": "https://github.com/NLeSC/spot",\n "datePublished": "2020-10-21",\n "identifier": "https://doi.org/10.5281/zenodo.4116521",\n "keywords": [\n "visualization",\n "big data",\n "visual data analytics",\n "multi-dimensional data"\n ],\n "license": "http://www.apache.org/licenses/LICENSE-2.0",\n "name": "spot",\n "version": "0.2.0"\n}' -// }] -// const rsdCitationItem = { -// 'id': 'e89b2d3d-7efc-44f3-810d-3a09a83de209', -// 'software': '03e46e16-fa53-4cb6-996d-4e8204c7c5af', -// 'is_citable': true, -// 'latest_schema_dot_org': '{\n "@context": "https://schema.org",\n "@type": "SoftwareSourceCode",\n "author": [\n {\n "@type": "Person",\n "affiliation": {\n "@type": "Organization",\n "legalName": "Netherlands eScience Center"\n },\n "familyName": "Attema",\n "givenName": "Jisk"\n },\n {\n "@id": "https://orcid.org/0000-0002-0989-929X",\n "@type": "Person",\n "affiliation": {\n "@type": "Organization",\n "legalName": "Netherlands eScience Center"\n },\n "familyName": "Diblen",\n "givenName": "Faruk"\n }\n ],\n "codeRepository": "https://github.com/NLeSC/spot",\n "datePublished": "2020-10-21",\n "identifier": "https://doi.org/10.5281/zenodo.4116521",\n "keywords": [\n "visualization",\n "big data",\n "visual data analytics",\n "multi-dimensional data"\n ],\n "license": "http://www.apache.org/licenses/LICENSE-2.0",\n "name": "spot",\n "version": "0.2.0"\n}', -// 'created_at': '2021-12-21T11:52:11.15736', -// 'updated_at': '2021-12-21T11:52:11.15736', -// 'release_content': rsdCitationItemContent -// } - -export type SoftwareCitationContent = { - id: string; - release_id: string; - citability: 'full'|'doi'; - date_published: string; - doi: string; - tag: string; - url: string; - bibtex: string; - cff: string; - codemeta: string; - endnote: string; - ris: string; - schema_dot_org: string; -} - -export type SoftwareCitationInfo = { - id: string; - software: string; - is_citable: boolean; - latest_schema_dot_org: string; - created_at: string; - updated_at: string; - release_content: SoftwareCitationContent[] -} - diff --git a/frontend/types/SoftwareTypes.ts b/frontend/types/SoftwareTypes.ts index 8a7826106..704286c7e 100644 --- a/frontend/types/SoftwareTypes.ts +++ b/frontend/types/SoftwareTypes.ts @@ -1,7 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2022 - 2023 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2022 - 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 dv4all // @@ -181,3 +181,12 @@ export type SoftwareForSoftware = { origin: string, relation: string } + +/** + * USER PROFILE SETTINGS + */ + +export type UserSettingsType = { + agree_terms: boolean, + notice_privacy_statement: boolean +} diff --git a/frontend/utils/editOrganisation.ts b/frontend/utils/editOrganisation.ts index 6863a6f1a..e0d181eb5 100644 --- a/frontend/utils/editOrganisation.ts +++ b/frontend/utils/editOrganisation.ts @@ -1,8 +1,8 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -77,7 +77,7 @@ export async function fetchRSDOrganisations({searchFor, rorIds, token, frontend} data: { ...item, source: 'RSD' - } + }, } }) return options diff --git a/frontend/utils/getSoftware.ts b/frontend/utils/getSoftware.ts index ef52791a7..f1c073220 100644 --- a/frontend/utils/getSoftware.ts +++ b/frontend/utils/getSoftware.ts @@ -1,16 +1,16 @@ -// SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2021 - 2022 dv4all +// SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2021 - 2023 dv4all // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2022 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 import {KeywordForSoftware, RepositoryInfo, SoftwareItem, SoftwareListItem} from '../types/SoftwareTypes' -import {SoftwareCitationInfo} from '../types/SoftwareCitation' import {extractCountFromHeader} from './extractCountFromHeader' import logger from './logger' -import {createJsonHeaders} from './fetchHelpers' +import {createJsonHeaders, getBaseUrl} from './fetchHelpers' import {RelatedProjectForSoftware} from '~/types/Project' +import {SoftwareReleaseInfo} from '~/components/organisation/releases/useSoftwareReleases' /* * Software list for the software overview page @@ -141,33 +141,33 @@ export async function getTagsWithCount(){ /** * CITATIONS - * @param uuid - * @returns SoftwareCitationInfo + * @param uuid as software_id + * @returns SoftwareReleaseInfo[] */ export async function getCitationsForSoftware(uuid:string,token?:string){ try{ - // this request is always perfomed from backend - // the release content is order by date_published - const url = `${process.env.POSTGREST_URL}/release?select=*,release_content(*)&software=eq.${uuid}&release_content.order=date_published.desc` + // the releases are order by date descending + const query = `software_id=eq.${uuid}&order=release_date.desc` + const url = `${getBaseUrl()}/rpc/software_release?${query}` const resp = await fetch(url, { method: 'GET', headers: createJsonHeaders(token) }) if (resp.status===200){ - const data: SoftwareCitationInfo[] = await resp.json() - // console.log('data...', data) + const data: SoftwareReleaseInfo[] = await resp.json() if (data.length > 0) { - return data[0] + return data } return null } else if (resp.status===404){ - logger(`getReleasesForSoftware: 404 [${url}]`,'error') + logger(`getCitationsForSoftware: 404 [${url}]`,'error') // query not found return null } + return null }catch(e:any){ - logger(`getReleasesForSoftware: ${e?.message}`,'error') + logger(`getCitationsForSoftware: ${e?.message}`,'error') return null } } diff --git a/scrapers/jobs.cron b/scrapers/jobs.cron index 38699c26a..4d2795dc3 100644 --- a/scrapers/jobs.cron +++ b/scrapers/jobs.cron @@ -5,6 +5,6 @@ */6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.git.MainProgrammingLanguages > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 2-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.git.MainLicenses > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 4-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.git.MainCommits > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 -1-59/6 * * * * /usr/bin/python3 -u /usr/myjava/releases.py > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 +1-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.doi.MainReleases > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 3-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.doi.MainMentions > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 0 1 * * * /usr/bin/python3 -u /usr/myjava/oaipmh.py > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/DataCiteReleaseRepository.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/DataCiteReleaseRepository.java new file mode 100644 index 000000000..af7c70533 --- /dev/null +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/DataCiteReleaseRepository.java @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +package nl.esciencecenter.rsd.scraper.doi; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import nl.esciencecenter.rsd.scraper.Utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +public class DataCiteReleaseRepository { + + private static final String QUERY_UNFORMATTED = """ + query { + works(ids: [%s]) { + nodes { + doi + versionOfCount + versions(first: 10000) { + nodes { + doi + types { + resourceType + resourceTypeGeneral + } + version + relatedIdentifiers { + relatedIdentifier + relatedIdentifierType + } + titles(first: 1) { + title + } + publisher + publicationYear + dates { + date + } + creators { + givenName + familyName + } + contributors { + givenName + familyName + } + } + } + } + } + } + """; + + public Map> getVersionedDois(Collection conceptDois) { + if (conceptDois.isEmpty()) return Collections.EMPTY_MAP; + + String query = QUERY_UNFORMATTED.formatted(DataciteMentionRepository.joinCollection(conceptDois)); + JsonObject body = new JsonObject(); + body.addProperty("query", query); + String responseJson = Utils.post("https://api.datacite.org/graphql", body.toString(), "Content-Type", "application/json"); + return parseJson(responseJson); + } + + Map> parseJson(String json) { + JsonObject root = JsonParser.parseString(json).getAsJsonObject(); + JsonArray worksJson = root.getAsJsonObject("data").getAsJsonObject("works").getAsJsonArray("nodes"); + Map> releasesPerConceptDoi = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (JsonElement work : worksJson) { + try { + JsonObject workObject = work.getAsJsonObject(); + String conceptDoi = workObject.getAsJsonPrimitive("doi").getAsString(); + Integer verionOfCount = Utils.integerOrNull(workObject.get("versionOfCount")); + if (verionOfCount == null || verionOfCount.intValue() != 0) { + System.out.println("%s is not a concept DOI".formatted(conceptDoi)); + continue; + } + + Collection versionedMentions = new ArrayList<>(); + JsonArray versionedMentionsJson = workObject.getAsJsonObject("versions").getAsJsonArray("nodes"); + versionedMentionsJson.forEach(versionedMentionJson -> { + MentionRecord versionedMention = DataciteMentionRepository.parseWork(versionedMentionJson.getAsJsonObject()); + versionedMentions.add(versionedMention); + }); + + releasesPerConceptDoi.put(conceptDoi, versionedMentions); + } catch (RuntimeException e) { + System.out.println("Failed to scrape a DataCite mention with data " + work); + e.printStackTrace(); + } + } + return releasesPerConceptDoi; + } +} diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/DataciteMentionRepository.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/DataciteMentionRepository.java index 75f35bcd9..1bc7e440c 100644 --- a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/DataciteMentionRepository.java +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/DataciteMentionRepository.java @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -13,13 +13,17 @@ import java.net.URI; import java.time.Instant; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; public class DataciteMentionRepository implements MentionRepository { @@ -34,11 +38,18 @@ public class DataciteMentionRepository implements MentionRepository { resourceTypeGeneral } version + relatedIdentifiers { + relatedIdentifier + relatedIdentifierType + } titles(first: 1) { title } publisher publicationYear + dates { + date + } creators { givenName familyName @@ -53,6 +64,7 @@ public class DataciteMentionRepository implements MentionRepository { private static final Map dataciteTypeMap; private static final Map dataciteTextTypeMap; + private static final Pattern URL_TREE_TAG_PATTERN = Pattern.compile("/tree/([^/]+)$"); static { // https://schema.datacite.org/meta/kernel-4.4/ @@ -105,7 +117,7 @@ static Collection jsonStringToUniqueMentions(String json) { JsonObject root = JsonParser.parseString(json).getAsJsonObject(); JsonArray worksJson = root.getAsJsonObject("data").getAsJsonObject("works").getAsJsonArray("nodes"); Collection mentions = new ArrayList<>(); - Set usedDois = new HashSet<>(); + Set usedDois = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); for (JsonElement work : worksJson) { try { // Sometimes, DataCite gives back two of the same results for one DOI, e.g. for 10.4122/1.1000000817, @@ -142,6 +154,11 @@ static MentionRecord parseWork(JsonObject work) { result.publisher = Utils.stringOrNull(work.get("publisher")); result.publicationYear = Utils.integerOrNull(work.get("publicationYear")); + JsonArray dates = work.getAsJsonArray("dates"); + if (!dates.isEmpty()) { + result.publicationDate = LocalDate.parse(dates.get(0).getAsJsonObject().getAsJsonPrimitive("date").getAsString()); + } + String dataciteResourceTypeGeneral = Utils.stringOrNull(work.getAsJsonObject("types").get("resourceTypeGeneral")); if (dataciteResourceTypeGeneral != null && dataciteResourceTypeGeneral.equals("Text")) { String dataciteResourceType = Utils.stringOrNull(work.getAsJsonObject("types").get("resourceType")); @@ -151,6 +168,23 @@ static MentionRecord parseWork(JsonObject work) { result.mentionType = dataciteTypeMap.getOrDefault(dataciteResourceTypeGeneral, MentionType.other); } result.source = "DataCite"; + result.version = Utils.stringOrNull(work.get("version")); + // if the version is null, we can often get the version from a linked Git URL which ends in "/tree/{tag}" + if (result.version == null) { + JsonArray relatedIdentifiers = work.getAsJsonArray("relatedIdentifiers"); + for (JsonElement relatedIdentifier : relatedIdentifiers) { + String relatedIdentifierString = Utils.stringOrNull(relatedIdentifier.getAsJsonObject().get("relatedIdentifier")); + String relatedIdentifierType = Utils.stringOrNull(relatedIdentifier.getAsJsonObject().get("relatedIdentifierType")); + if (relatedIdentifierString != null && relatedIdentifierType != null && relatedIdentifierType.equals("URL")) { + Matcher tagMatcher = URL_TREE_TAG_PATTERN.matcher(relatedIdentifierString); + if (tagMatcher.find()) { + result.version = tagMatcher.group(1); + break; + } + } + } + } + result.scrapedAt = Instant.now(); return result; } @@ -180,7 +214,7 @@ public Collection mentionData(Collection dois) { } @Override - public void save(Collection mentions) { + public Map save(Collection mentions) { throw new UnsupportedOperationException(); } diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MainReleases.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MainReleases.java new file mode 100644 index 000000000..d9c50affc --- /dev/null +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MainReleases.java @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +package nl.esciencecenter.rsd.scraper.doi; + +import nl.esciencecenter.rsd.scraper.Config; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +/* + * 1. Get the least recently scraped releases from software with a concept DOI. We also check for existing releases that already exist as a mention in the database, so we don't have to (TODO) recreate them later. + * 2. For each release check if it's a concept DOI on DataCite and get all the versioned DOIs. + * 3. For each versioned DOI, get its metadata from DataCite. If this versioned DOI did not exist as a mention, create it using the mention scraper. Add each versioned DOI as an entry in the release_content table + */ +public class MainReleases { + + public static void main(String[] args) { + System.out.println("Start scraping releases"); + PostgrestReleaseRepository releaseRepository = new PostgrestReleaseRepository(Config.backendBaseUrl()); + + Collection releasesToScrape = releaseRepository.leastRecentlyScrapedReleases(Config.maxRequestsDoi()); + + Collection conceptDoisToScrape = releasesToScrape.stream() + .map(releaseData -> releaseData.conceptDoi) + .toList(); + + Map> scrapedReleasesPerConceptDoi = new DataCiteReleaseRepository().getVersionedDois(conceptDoisToScrape); + + MentionRepository localMentionRepository = new PostgrestMentionRepository(Config.backendBaseUrl()); + Collection allMentions = scrapedReleasesPerConceptDoi.values().stream() + .flatMap(Collection::stream) + .toList(); + Map doiToId = localMentionRepository.save(allMentions); + + releaseRepository.saveReleaseContent(releasesToScrape, scrapedReleasesPerConceptDoi, doiToId); + System.out.println("Done scraping releases"); + } +} diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRecord.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRecord.java index e5c6b9963..59a678de5 100644 --- a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRecord.java +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRecord.java @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -7,6 +7,7 @@ import java.net.URI; import java.time.Instant; +import java.time.LocalDate; import java.util.UUID; public class MentionRecord { @@ -17,9 +18,11 @@ public class MentionRecord { String authors; String publisher; Integer publicationYear; + LocalDate publicationDate; String page; URI imageUrl; MentionType mentionType; String source; Instant scrapedAt; + String version; } diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRepository.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRepository.java index df6929cfc..4c7af7529 100644 --- a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRepository.java +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/MentionRepository.java @@ -1,11 +1,13 @@ -// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 package nl.esciencecenter.rsd.scraper.doi; import java.util.Collection; +import java.util.Map; +import java.util.UUID; public interface MentionRepository { @@ -13,5 +15,5 @@ public interface MentionRepository { Collection mentionData(Collection dois); - void save(Collection mentions); + Map save(Collection mentions); } diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/PostgrestMentionRepository.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/PostgrestMentionRepository.java index 8ecfae069..a3dc8c2c8 100644 --- a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/PostgrestMentionRepository.java +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/PostgrestMentionRepository.java @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -7,7 +7,10 @@ import com.google.gson.FieldNamingPolicy; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializer; import com.google.gson.reflect.TypeToken; @@ -16,8 +19,12 @@ import java.net.URI; import java.time.Instant; +import java.time.LocalDate; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.UUID; public class PostgrestMentionRepository implements MentionRepository { @@ -30,8 +37,9 @@ public PostgrestMentionRepository(String backendUrl) { static Collection parseJson(String data) { return new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .registerTypeAdapter(Instant.class, (JsonDeserializer) (json, typeOfT, context) -> Instant.parse(json.getAsString())) - .registerTypeAdapter(URI.class, (JsonDeserializer) (json, typeOfT, context) -> { + .registerTypeAdapter(Instant.class, (JsonDeserializer) (json, typeOfT, context) -> Instant.parse(json.getAsString())) + .registerTypeAdapter(LocalDate.class, (JsonDeserializer) (json, typeOfT, context) -> LocalDate.parse(json.getAsString())) + .registerTypeAdapter(URI.class, (JsonDeserializer) (json, typeOfT, context) -> { try { return URI.create(json.getAsString()); } catch (IllegalArgumentException e) { @@ -56,12 +64,23 @@ public Collection mentionData(Collection dois) { } @Override - public void save(Collection mentions) { + public Map save(Collection mentions) { String scrapedMentionsJson = new GsonBuilder() .serializeNulls() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .registerTypeAdapter(Instant.class, (JsonSerializer) (src, typeOfSrc, context) -> new JsonPrimitive(((Instant) src).toString())) + .registerTypeAdapter(Instant.class, (JsonSerializer) (src, typeOfSrc, context) -> new JsonPrimitive(src.toString())) + .registerTypeAdapter(LocalDate.class, (JsonSerializer) (src, typeOfSrc, context) -> new JsonPrimitive(src.toString())) .create().toJson(mentions); - Utils.postAsAdmin(Config.backendBaseUrl() + "/mention?on_conflict=doi", scrapedMentionsJson, "Prefer", "resolution=merge-duplicates"); + String response = Utils.postAsAdmin(Config.backendBaseUrl() + "/mention?on_conflict=doi&select=doi,id", scrapedMentionsJson, "Prefer", "resolution=merge-duplicates,return=representation"); + + JsonArray responseAsArray = JsonParser.parseString(response).getAsJsonArray(); + Map doiToId = new HashMap<>(); + for (JsonElement jsonElement : responseAsArray) { + String doi = jsonElement.getAsJsonObject().getAsJsonPrimitive("doi").getAsString(); + UUID id = UUID.fromString(jsonElement.getAsJsonObject().getAsJsonPrimitive("id").getAsString()); + doiToId.put(doi, id); + } + + return doiToId; } } diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/PostgrestReleaseRepository.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/PostgrestReleaseRepository.java new file mode 100644 index 000000000..69118127f --- /dev/null +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/PostgrestReleaseRepository.java @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +package nl.esciencecenter.rsd.scraper.doi; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import nl.esciencecenter.rsd.scraper.Config; +import nl.esciencecenter.rsd.scraper.Utils; + +import java.time.Instant; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.UUID; + +public class PostgrestReleaseRepository { + + private final String backendUrl; + + public PostgrestReleaseRepository(String backendUrl) { + this.backendUrl = Objects.requireNonNull(backendUrl); + } + + public Collection leastRecentlyScrapedReleases(int limit) { + String data = Utils.getAsAdmin(backendUrl + "/rpc/software_join_release?concept_doi=not.is.null&order=releases_scraped_at.asc.nullsfirst&limit=" + limit); + return parseJson(data); + } + + public void saveReleaseContent(Collection releaseData, Map> conceptDoiToDois, Map versionDoiToMentionId) { + // First update the releases_scraped_at column. + JsonArray releasesBody = new JsonArray(); + Instant now = Instant.now(); + for (ReleaseData release : releaseData) { + JsonObject data = new JsonObject(); + data.addProperty("software", release.softwareId.toString()); + data.addProperty("releases_scraped_at", now.toString()); + releasesBody.add(data); + } + Utils.postAsAdmin(Config.backendBaseUrl() + "/release", releasesBody.toString(), "Prefer", "resolution=merge-duplicates"); + + + // Then update the release_version table. + // For each scraped or existing version as a mention, we need to know its id of the mention table and the id of the software to which it belongs. + Map conceptDoiToSoftwareId = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (ReleaseData release : releaseData) { + conceptDoiToSoftwareId.put(release.conceptDoi, release.softwareId); + } + + Map versionDoiToConceptDoi = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (Map.Entry> conceptDoiToDoisEntry : conceptDoiToDois.entrySet()) { + String conceptDoi = conceptDoiToDoisEntry.getKey(); + Collection versionDois = conceptDoiToDoisEntry.getValue(); + for (MentionRecord version : versionDois) { + versionDoiToConceptDoi.put(version.doi, conceptDoi); + } + } + + JsonArray coupling = new JsonArray(); + for (String versionDoi : versionDoiToConceptDoi.keySet()) { + JsonObject couple = new JsonObject(); + couple.addProperty("release_id", conceptDoiToSoftwareId.get(versionDoiToConceptDoi.get(versionDoi)).toString()); + couple.addProperty("mention_id", versionDoiToMentionId.get(versionDoi).toString()); + coupling.add(couple); + } + + Utils.postAsAdmin(Config.backendBaseUrl() + "/release_version", coupling.toString(), "Prefer", "resolution=merge-duplicates"); + } + + Collection parseJson(String data) { + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + TypeToken> typeToken = new TypeToken>() {}; + return gson.fromJson(data, typeToken.getType()); + } +} diff --git a/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/ReleaseData.java b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/ReleaseData.java new file mode 100644 index 000000000..45106375c --- /dev/null +++ b/scrapers/src/main/java/nl/esciencecenter/rsd/scraper/doi/ReleaseData.java @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +package nl.esciencecenter.rsd.scraper.doi; + +import java.util.Collection; +import java.util.UUID; + +public class ReleaseData { + + public UUID softwareId; + public String slug; + public String conceptDoi; + public Collection versionedDois; + + @Override + public String toString() { + return "ReleaseData{" + + "softwareId=" + softwareId + + ", slug='" + slug + '\'' + + ", conceptDoi='" + conceptDoi + '\'' + + ", versionedDois=" + versionedDois + + '}'; + } +}