From dd90b97584b8b30f322ee1263fbea27ab1b10a52 Mon Sep 17 00:00:00 2001 From: jrcastro2 Date: Wed, 11 Sep 2024 13:37:31 +0200 Subject: [PATCH 01/76] fixtures: allow to load new vocab --- invenio_rdm_records/fixtures/vocabularies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invenio_rdm_records/fixtures/vocabularies.py b/invenio_rdm_records/fixtures/vocabularies.py index cf7467baa..f82cc7e03 100644 --- a/invenio_rdm_records/fixtures/vocabularies.py +++ b/invenio_rdm_records/fixtures/vocabularies.py @@ -192,7 +192,8 @@ def load(self, reload=None): for v in VocabularyScheme.query.options(load_only("id", "parent_id")).all() ] self._loaded_vocabularies = set(v_type_ids + v_subtype_ids) - if reload: + # If it's not already loaded it means it's a new one + if reload and reload in self._loaded_vocabularies: self._loaded_vocabularies.remove(reload) # 1- Load from app_data_folder filepath = self._app_data_folder / self._filename From db62df7e36671868b9ab4e7511cdb32d83222c71 Mon Sep 17 00:00:00 2001 From: jrcastro2 Date: Wed, 11 Sep 2024 13:37:52 +0200 Subject: [PATCH 02/76] release: v12.1.1 --- CHANGES.rst | 5 +++++ invenio_rdm_records/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index e086f87bc..b1f588f2d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,11 @@ Changes ======= +Version v12.1.1 (released 2024-09-11) + +- resource: fix add record to community +- controls: refactored isDisabled function + Version v12.1.0 (released 2024-08-30) - config: added links for thumbnails (#1799) diff --git a/invenio_rdm_records/__init__.py b/invenio_rdm_records/__init__.py index fa764db52..62555abe9 100644 --- a/invenio_rdm_records/__init__.py +++ b/invenio_rdm_records/__init__.py @@ -10,6 +10,6 @@ from .ext import InvenioRDMRecords -__version__ = "12.1.0" +__version__ = "12.1.1" __all__ = ("__version__", "InvenioRDMRecords") From 9b69c9d9d2f32c9ff933425f9a5ccafb090e6371 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 18 Apr 2024 12:02:55 +0200 Subject: [PATCH 03/76] tests: add serializer test for empty record --- tests/conftest.py | 13 +++++++++++++ tests/resources/serializers/test_csl_serializer.py | 11 +++++++++++ .../serializers/test_datacite_serializer.py | 11 +++++++++++ .../serializers/test_dublincore_serializer.py | 11 +++++++++++ .../serializers/test_schemaorg_serializer.py | 13 +++++++++++++ 5 files changed, 59 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 29580de56..8a59f5b48 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -918,6 +918,19 @@ def minimal_record(): } +@pytest.fixture() +def empty_record(): + """Almost empty record data as dict coming from the external world.""" + return { + "pids": {}, + "access": {}, + "files": { + "enabled": False, # Most tests don't care about files + }, + "metadata": {}, + } + + @pytest.fixture() def minimal_restricted_record(minimal_record): """Data for restricted record.""" diff --git a/tests/resources/serializers/test_csl_serializer.py b/tests/resources/serializers/test_csl_serializer.py index 97abe2378..319f63a3c 100644 --- a/tests/resources/serializers/test_csl_serializer.py +++ b/tests/resources/serializers/test_csl_serializer.py @@ -148,3 +148,14 @@ def test_citation_string_serializer_record( # in case of error, the response is JSON assert response.headers["content-type"] == "application/json" assert f"Citation string style not found." in body + + +def test_citation_string_serializer_empty_record(running_app, empty_record): + """Test Citation String Serializer for an empty record.""" + + expected_data = {} + + serializer = CSLJSONSchema() + serialized_record = serializer.dump(empty_record) + + assert serialized_record == expected_data diff --git a/tests/resources/serializers/test_datacite_serializer.py b/tests/resources/serializers/test_datacite_serializer.py index 12a7432c9..967829aec 100644 --- a/tests/resources/serializers/test_datacite_serializer.py +++ b/tests/resources/serializers/test_datacite_serializer.py @@ -513,3 +513,14 @@ def test_datacite43_serializer_updated_date(running_app, full_modified_date_reco assert expected_dates == serialized_record["dates"] assert len(serialized_record["dates"]) == 3 + + +def test_datacite43_serializer_empty_record(running_app, empty_record): + """Test if the DataCite 4.3 JSON serializer handles an empty record.""" + + expected_data = {"schemaVersion": "http://datacite.org/schema/kernel-4"} + + serializer = DataCite43JSONSerializer() + serialized_record = serializer.dump_obj(empty_record) + + assert serialized_record == expected_data diff --git a/tests/resources/serializers/test_dublincore_serializer.py b/tests/resources/serializers/test_dublincore_serializer.py index 129914d4a..52c327d3d 100644 --- a/tests/resources/serializers/test_dublincore_serializer.py +++ b/tests/resources/serializers/test_dublincore_serializer.py @@ -90,6 +90,17 @@ def test_dublincorejson_serializer_minimal(running_app, updated_minimal_record): assert serialized_record == expected_data +def test_dublincorejson_serializer_empty_record(running_app, empty_record): + """Test serializer to Dublin Core JSON with an empty record""" + + expected_data = {} + + serializer = DublinCoreJSONSerializer() + serialized_record = serializer.dump_obj(empty_record) + + assert serialized_record == expected_data + + def test_vocabulary_type_error(running_app, updated_minimal_record): """Test error thrown on missing resource type.""" updated_minimal_record["metadata"]["resource_type"]["id"] = "invalid" diff --git a/tests/resources/serializers/test_schemaorg_serializer.py b/tests/resources/serializers/test_schemaorg_serializer.py index b61122f64..6569a91ab 100644 --- a/tests/resources/serializers/test_schemaorg_serializer.py +++ b/tests/resources/serializers/test_schemaorg_serializer.py @@ -178,3 +178,16 @@ def test_schemaorg_serializer_minimal_record(running_app, minimal_record): serialized_record = serializer.dump_obj(minimal_record) assert serialized_record == expected_data + + +def test_schemaorg_serializer_empty_record(running_app, empty_record): + """Test Schemaorg JSON-LD serializer with minimal record.""" + + expected_data = { + "@context": "http://schema.org", + } + + serializer = SchemaorgJSONLDSerializer() + serialized_record = serializer.dump_obj(empty_record) + + assert serialized_record == expected_data From dc1e868ade727b9764fb119f9eb3d2e0dccb085d Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 18 Apr 2024 11:15:20 +0200 Subject: [PATCH 04/76] serializers: ensure values are not None before access --- .../resources/serializers/csl/schema.py | 13 ++++- .../resources/serializers/datacite/schema.py | 17 ++++-- .../serializers/dublincore/schema.py | 35 +++++++----- .../resources/serializers/marcxml/schema.py | 54 ++++++++++--------- .../resources/serializers/schemaorg/schema.py | 12 ++++- .../resources/serializers/schemas.py | 8 ++- .../resources/serializers/ui/schema.py | 20 ++++--- 7 files changed, 106 insertions(+), 53 deletions(-) diff --git a/invenio_rdm_records/resources/serializers/csl/schema.py b/invenio_rdm_records/resources/serializers/csl/schema.py index a0cce3056..e6d0f24ca 100644 --- a/invenio_rdm_records/resources/serializers/csl/schema.py +++ b/invenio_rdm_records/resources/serializers/csl/schema.py @@ -13,6 +13,7 @@ from flask_resources.serializers import BaseSerializerSchema from marshmallow import Schema, fields, missing, pre_dump from marshmallow_utils.fields import SanitizedUnicode, StrippedHTML +from pydash import py_ from ..schemas import CommonFieldsMixin from ..utils import get_vocabulary_props @@ -62,19 +63,27 @@ class CSLJSONSchema(BaseSerializerSchema, CommonFieldsMixin): def get_type(self, obj): """Get resource type.""" + resource_type_id = py_.get(obj, "metadata.resource_type.id") + if not resource_type_id: + return missing + props = get_vocabulary_props( "resourcetypes", [ "props.csl", ], - obj["metadata"]["resource_type"]["id"], + resource_type_id, ) return props.get("csl", "article") # article is CSL "Other" def get_issued(self, obj): """Get issued dates.""" + publication_date = py_.get(obj, "metadata.publication_date") + if not publication_date: + return missing + try: - parsed = parse_edtf(obj["metadata"].get("publication_date")) + parsed = parse_edtf(publication_date) except EDTFParseException: return missing diff --git a/invenio_rdm_records/resources/serializers/datacite/schema.py b/invenio_rdm_records/resources/serializers/datacite/schema.py index 17ed58d33..82d685e41 100644 --- a/invenio_rdm_records/resources/serializers/datacite/schema.py +++ b/invenio_rdm_records/resources/serializers/datacite/schema.py @@ -20,6 +20,7 @@ from marshmallow import Schema, ValidationError, fields, missing, post_dump, validate from marshmallow_utils.fields import SanitizedUnicode from marshmallow_utils.html import strip_html +from pydash import py_ from ....proxies import current_rdm_records_service from ...serializers.ui.schema import current_default_locale @@ -206,10 +207,14 @@ class DataCite43Schema(BaseSerializerSchema): def get_type(self, obj): """Get resource type.""" + resource_type_id = py_.get(obj, "metadata.resource_type.id") + if not resource_type_id: + return missing + props = get_vocabulary_props( "resourcetypes", ["props.datacite_general", "props.datacite_type"], - obj["metadata"]["resource_type"]["id"], + resource_type_id, ) return { "resourceTypeGeneral": props.get("datacite_general", "Other"), @@ -261,8 +266,11 @@ def get_descriptions(self, obj): def get_publication_year(self, obj): """Get publication year from edtf date.""" + publication_date = py_.get(obj, "metadata.publication_date") + if not publication_date: + return missing + try: - publication_date = obj["metadata"]["publication_date"] parsed_date = parse_edtf(publication_date) return str(parsed_date.lower_strict().tm_year) except ParseException: @@ -274,7 +282,8 @@ def get_publication_year(self, obj): def get_dates(self, obj): """Get dates.""" - dates = [{"date": obj["metadata"]["publication_date"], "dateType": "Issued"}] + pub_date = py_.get(obj, "metadata.publication_date") + dates = [{"date": pub_date, "dateType": "Issued"}] if pub_date else [] updated = False @@ -428,7 +437,7 @@ def get_related_identifiers(self, obj): if hasattr(obj, "parent"): parent_record = obj.parent else: - parent_record = obj["parent"] + parent_record = obj.get("parent", {}) parent_doi = parent_record.get("pids", {}).get("doi") if parent_doi: diff --git a/invenio_rdm_records/resources/serializers/dublincore/schema.py b/invenio_rdm_records/resources/serializers/dublincore/schema.py index 48cbc2745..541b762a7 100644 --- a/invenio_rdm_records/resources/serializers/dublincore/schema.py +++ b/invenio_rdm_records/resources/serializers/dublincore/schema.py @@ -12,6 +12,7 @@ from flask import current_app from flask_resources.serializers import BaseSerializerSchema from marshmallow import fields, missing +from pydash import py_ from ..schemas import CommonFieldsMixin from ..ui.schema import current_default_locale @@ -91,22 +92,22 @@ def get_relations(self, obj): # FIXME: Add after UI support is there # Alternate identifiers - for a in obj["metadata"].get("alternate_identifiers", []): + for a in obj.get("metadata", {}).get("alternate_identifiers", []): rels.append(self._transform_identifier(a["identifier"], a["scheme"])) # Related identifiers - for a in obj["metadata"].get("related_identifiers", []): + for a in obj.get("metadata", {}).get("related_identifiers", []): rels.append(self._transform_identifier(a["identifier"], a["scheme"])) # Communities - communities = obj["parent"].get("communities", {}).get("entries", []) + communities = obj.get("parent", {}).get("communities", {}).get("entries", []) for community in communities: slug = community["slug"] url = f"{current_app.config['SITE_UI_URL']}/communities/{slug}" rels.append(self._transform_identifier(url, "url")) # Parent doi - parent_pids = obj["parent"].get("pids", {}) + parent_pids = obj.get("parent", {}).get("pids", {}) for key, value in parent_pids.items(): if key == "doi": rels.append(self._transform_identifier(value["identifier"], key)) @@ -117,13 +118,14 @@ def get_rights(self, obj): """Get rights.""" rights = [] - access_right = obj["access"]["status"] - if access_right == "metadata-only": - access_right = "closed" + access_right = py_.get(obj, "access.status") + if access_right: + if access_right == "metadata-only": + access_right = "closed" - rights.append(f"info:eu-repo/semantics/{access_right}Access") + rights.append(f"info:eu-repo/semantics/{access_right}Access") - for right in obj["metadata"].get("rights", []): + for right in obj.get("metadata", {}).get("rights", []): rights.append(right.get("title").get(current_default_locale())) if right.get("id"): license_url = right.get("props", {}).get("url") @@ -138,9 +140,14 @@ def get_rights(self, obj): def get_dates(self, obj): """Get dates.""" - dates = [obj["metadata"]["publication_date"]] + dates = [] - if obj["access"]["status"] == "embargoed": + publication_date = py_.get(obj, "metadata.publication_date") + if publication_date: + dates.append(publication_date) + + access_right = py_.get(obj, "access.status") + if access_right == "embargoed": date = obj["access"]["embargo"]["until"] dates.append(f"info:eu-repo/date/embargoEnd/{date}") @@ -181,12 +188,16 @@ def get_subjects(self, obj): def get_types(self, obj): """Get resource type.""" + resource_type_id = py_.get(obj, "metadata.resource_type.id") + if not resource_type_id: + return missing + props = get_vocabulary_props( "resourcetypes", [ "props.eurepo", ], - obj["metadata"]["resource_type"]["id"], + resource_type_id, ) t = props.get("eurepo") return [t] if t else missing diff --git a/invenio_rdm_records/resources/serializers/marcxml/schema.py b/invenio_rdm_records/resources/serializers/marcxml/schema.py index f2e70cebe..4af87ea17 100644 --- a/invenio_rdm_records/resources/serializers/marcxml/schema.py +++ b/invenio_rdm_records/resources/serializers/marcxml/schema.py @@ -14,6 +14,7 @@ from flask_resources.serializers import BaseSerializerSchema from marshmallow import fields, missing from marshmallow_utils.html import sanitize_unicode +from pydash import py_ from ..schemas import CommonFieldsMixin from ..ui.schema import current_default_locale @@ -491,30 +492,33 @@ def get_types_and_communities(self, obj): if communities: slugs = [community.get("slug") for community in communities] output += [{"a": f"user-{slug}"} for slug in slugs] - props = get_vocabulary_props( - "resourcetypes", - [ - "props.eurepo", - "props.marc21_type", - "props.marc21_subtype", - ], - obj["metadata"]["resource_type"]["id"], - ) - props_eurepo = props.get("eurepo") - if props_eurepo: - eurepo = {"a": props_eurepo} - output.append(eurepo) - - resource_types = {} - - resource_type = props.get("marc21_type") - if resource_type: - resource_types["a"] = resource_type - resource_subtype = props.get("marc21_subtype") - if resource_subtype: - resource_types["b"] = resource_subtype - - if resource_types: - output.append(resource_types) + + resource_type_id = py_.get(obj, "metadata.resource_type.id") + if resource_type_id: + props = get_vocabulary_props( + "resourcetypes", + [ + "props.eurepo", + "props.marc21_type", + "props.marc21_subtype", + ], + resource_type_id, + ) + props_eurepo = props.get("eurepo") + if props_eurepo: + eurepo = {"a": props_eurepo} + output.append(eurepo) + + resource_types = {} + + resource_type = props.get("marc21_type") + if resource_type: + resource_types["a"] = resource_type + resource_subtype = props.get("marc21_subtype") + if resource_subtype: + resource_types["b"] = resource_subtype + + if resource_types: + output.append(resource_types) return output or missing diff --git a/invenio_rdm_records/resources/serializers/schemaorg/schema.py b/invenio_rdm_records/resources/serializers/schemaorg/schema.py index 15d25f1f5..af1cdcad0 100644 --- a/invenio_rdm_records/resources/serializers/schemaorg/schema.py +++ b/invenio_rdm_records/resources/serializers/schemaorg/schema.py @@ -209,10 +209,14 @@ def get_id(self, obj): def get_type(self, obj): """Get type. Use the vocabulary service to get the schema.org type.""" + resource_type_id = py_.get(obj, "metadata.resource_type.id") + if not resource_type_id: + return missing + props = get_vocabulary_props( "resourcetypes", ["props.schema.org"], - py_.get(obj, "metadata.resource_type.id"), + resource_type_id, ) ret = props.get("schema.org", "https://schema.org/CreativeWork") return ret @@ -232,8 +236,12 @@ def get_format(self, obj): def get_publication_date(self, obj): """Get publication date.""" + publication_date = py_.get(obj, "metadata.publication_date") + if not publication_date: + return missing + try: - parsed_date = parse_edtf(py_.get(obj, "metadata.publication_date")) + parsed_date = parse_edtf(publication_date) except ParseException: return missing diff --git a/invenio_rdm_records/resources/serializers/schemas.py b/invenio_rdm_records/resources/serializers/schemas.py index 6d6b661b4..0a7034d56 100644 --- a/invenio_rdm_records/resources/serializers/schemas.py +++ b/invenio_rdm_records/resources/serializers/schemas.py @@ -8,6 +8,7 @@ """Base parsing functions for the various serializers.""" from marshmallow import missing +from pydash import py_ class CommonFieldsMixin: @@ -55,7 +56,8 @@ def get_locations(self, obj): def get_titles(self, obj): """Get titles.""" - return [obj["metadata"]["title"]] + title = py_.get(obj, "metadata.title") + return [title] if title else missing def get_identifiers(self, obj): """Get identifiers.""" @@ -67,7 +69,9 @@ def get_identifiers(self, obj): def get_creators(self, obj): """Get creators.""" - return [c["person_or_org"]["name"] for c in obj["metadata"].get("creators", [])] + return [ + c["person_or_org"]["name"] for c in obj["metadata"].get("creators", []) + ] or missing def get_publishers(self, obj): """Get publishers.""" diff --git a/invenio_rdm_records/resources/serializers/ui/schema.py b/invenio_rdm_records/resources/serializers/ui/schema.py index 8f6fa6557..05395550a 100644 --- a/invenio_rdm_records/resources/serializers/ui/schema.py +++ b/invenio_rdm_records/resources/serializers/ui/schema.py @@ -14,6 +14,7 @@ from functools import partial from babel_edtf import parse_edtf +from edtf.parser.grammar import ParseException from flask import current_app, g from flask_resources import BaseObjectSchema from invenio_communities.communities.resources.ui_schema import ( @@ -30,6 +31,7 @@ from marshmallow_utils.fields import FormatEDTF as FormatEDTF_ from marshmallow_utils.fields import SanitizedHTML, SanitizedUnicode, StrippedHTML from marshmallow_utils.fields.babel import gettext_from_dict +from pyparsing import ParseException from .fields import AccessStatusField @@ -218,12 +220,18 @@ def _format_journal(journal, publication_date): journal_issue = journal.get("issue") journal_volume = journal.get("volume") journal_pages = journal.get("pages") - publication_date_edtf = ( - parse_edtf(publication_date).lower_strict() if publication_date else None - ) - publication_date_formatted = ( - f"{publication_date_edtf.tm_year}" if publication_date_edtf else None - ) + + try: + publication_date_edtf = ( + parse_edtf(publication_date).lower_strict() + if publication_date + else None + ) + publication_date_formatted = ( + f"{publication_date_edtf.tm_year}" if publication_date_edtf else None + ) + except ParseException: + publication_date_formatted = None title = f"{journal_title}" if journal_title else None vol_issue = f"{journal_volume}" if journal_volume else None From fa829a10002069b1d4cd1641e8413c59a48861d0 Mon Sep 17 00:00:00 2001 From: Sam Arbid Date: Sat, 14 Sep 2024 16:43:57 +0200 Subject: [PATCH 05/76] UI: Handle empty file uploads in FileUploader * Display a warning for empty files, indicating they won't be included and listing file names. * Feature controlled by `records-resources-allow-empty-files` config value. * Continue uploading other files while showing the warning message. --- .../fields/FileUploader/FileUploader.js | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js b/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js index 1fd582bab..d829b63b8 100644 --- a/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js +++ b/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js @@ -3,6 +3,7 @@ // Copyright (C) 2020-2022 Northwestern University. // Copyright (C) 2022 Graz University of Technology. // Copyright (C) 2022 TU Wien. +// Copyright (C) 2024 KTH Royal Institute of Technology. // // Invenio-RDM-Records is free software; you can redistribute it and/or modify it // under the terms of the MIT License; see LICENSE file for more details. @@ -40,6 +41,7 @@ export const FileUploaderComponent = ({ isFileImportInProgress, decimalSizeDisplay, filesLocked, + allowEmptyFiles, ...uiProps }) => { // We extract the working copy of the draft stored as `values` in formik @@ -80,10 +82,28 @@ export const FileUploaderComponent = ({ const maxFileStorageReached = filesSize + acceptedFilesSize > quota.maxStorage; const filesNames = _map(filesList, "name"); - const duplicateFiles = acceptedFiles.filter((acceptedFile) => - filesNames.includes(acceptedFile.name) + const filesNamesSet = new Set(filesNames); + + const { duplicateFiles, emptyFiles, nonEmptyFiles } = acceptedFiles.reduce( + (accumulators, file) => { + if (filesNamesSet.has(file.name)) { + accumulators.duplicateFiles.push(file); + } + + if (file.size === 0) { + accumulators.emptyFiles.push(file); + } else { + accumulators.nonEmptyFiles.push(file); + } + + return accumulators; + }, + { duplicateFiles: [], emptyFiles: [], nonEmptyFiles: [] } ); + const hasEmptyFiles = !_isEmpty(emptyFiles); + const hasNonEmptyFiles = !_isEmpty(nonEmptyFiles); + if (maxFileNumberReached) { setWarningMsg(
@@ -130,7 +150,24 @@ export const FileUploaderComponent = ({
); } else { - uploadFiles(formikDraft, acceptedFiles); + if (!allowEmptyFiles && hasEmptyFiles) { + setWarningMsg( +
+ +
+ ); + } + + // Proceed with uploading the non-empty files or all files if empty files are allowed + if (allowEmptyFiles || hasNonEmptyFiles) { + uploadFiles(formikDraft, allowEmptyFiles ? acceptedFiles : nonEmptyFiles); + } } }, multiple: true, @@ -348,6 +385,7 @@ FileUploaderComponent.propTypes = { decimalSizeDisplay: PropTypes.bool, filesLocked: PropTypes.bool, permissions: PropTypes.object, + allowEmptyFiles: PropTypes.bool, }; FileUploaderComponent.defaultProps = { @@ -369,4 +407,5 @@ FileUploaderComponent.defaultProps = { importButtonText: i18next.t("Import files"), decimalSizeDisplay: true, filesLocked: false, + allowEmptyFiles: true, }; From 6d226ce7865e0deb0a3e23fe55a641dfb033567b Mon Sep 17 00:00:00 2001 From: Sam Arbid Date: Sat, 14 Sep 2024 22:43:36 +0200 Subject: [PATCH 06/76] Refactor: Enhance Warnings Handling * Add the missing translation tags. * Refactor the logic to display multiple warning messages, while allowing other files to upload, except for case where exceeding the file size limit. --- .../fields/FileUploader/FileUploader.js | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js b/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js index d829b63b8..eb787e306 100644 --- a/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js +++ b/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploader.js @@ -88,9 +88,7 @@ export const FileUploaderComponent = ({ (accumulators, file) => { if (filesNamesSet.has(file.name)) { accumulators.duplicateFiles.push(file); - } - - if (file.size === 0) { + } else if (file.size === 0) { accumulators.emptyFiles.push(file); } else { accumulators.nonEmptyFiles.push(file); @@ -102,7 +100,7 @@ export const FileUploaderComponent = ({ ); const hasEmptyFiles = !_isEmpty(emptyFiles); - const hasNonEmptyFiles = !_isEmpty(nonEmptyFiles); + const hasDuplicateFiles = !_isEmpty(duplicateFiles); if (maxFileNumberReached) { setWarningMsg( @@ -110,10 +108,12 @@ export const FileUploaderComponent = ({ ); @@ -123,7 +123,7 @@ export const FileUploaderComponent = ({ {i18next.t("Uploading the selected files would result in")}{" "} @@ -138,35 +138,43 @@ export const FileUploaderComponent = ({ /> ); - } else if (!_isEmpty(duplicateFiles)) { - setWarningMsg( -
+ } else { + let warnings = []; + + if (hasDuplicateFiles) { + warnings.push( -
- ); - } else { + ); + } + if (!allowEmptyFiles && hasEmptyFiles) { - setWarningMsg( -
- -
+ warnings.push( + ); } - // Proceed with uploading the non-empty files or all files if empty files are allowed - if (allowEmptyFiles || hasNonEmptyFiles) { - uploadFiles(formikDraft, allowEmptyFiles ? acceptedFiles : nonEmptyFiles); + if (!_isEmpty(warnings)) { + setWarningMsg(
{warnings}
); + } + + const filesToUpload = allowEmptyFiles + ? [...nonEmptyFiles, ...emptyFiles] + : nonEmptyFiles; + + // Proceed with uploading files if there are any to upload + if (!_isEmpty(filesToUpload)) { + uploadFiles(formikDraft, filesToUpload); } } }, From ea9aceb7a7072c3edad9777eefc8ff692b1ffbe3 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Wed, 10 Jul 2024 13:16:29 +0200 Subject: [PATCH 07/76] deposit: check permission and set disable tooltip for publish button --- .../controls/PublishButton/PublishButton.js | 64 +++++++++++++++---- .../SubmitReviewOrPublishButton.js | 5 +- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/controls/PublishButton/PublishButton.js b/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/controls/PublishButton/PublishButton.js index 71dafb704..e92293e52 100644 --- a/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/controls/PublishButton/PublishButton.js +++ b/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/controls/PublishButton/PublishButton.js @@ -1,6 +1,7 @@ // This file is part of Invenio-RDM-Records // Copyright (C) 2020-2023 CERN. // Copyright (C) 2020-2022 Northwestern University. +// Copyright (C) 2024 Graz University of Technology. // // Invenio-RDM-Records is free software; you can redistribute it and/or modify it // under the terms of the MIT License; see LICENSE file for more details. @@ -12,7 +13,7 @@ import _omit from "lodash/omit"; import PropTypes from "prop-types"; import React, { Component } from "react"; import { connect } from "react-redux"; -import { Button, Icon, Message, Modal } from "semantic-ui-react"; +import { Button, Icon, Message, Modal, Popup } from "semantic-ui-react"; import { DepositFormSubmitActions, DepositFormSubmitContext, @@ -40,8 +41,8 @@ class PublishButtonComponent extends Component { this.closeConfirmModal(); }; - isDisabled = (values, isSubmitting, filesState) => { - if (isSubmitting) { + isDisabled = (values, isSubmitting, filesState, hasPublishPermission) => { + if (isSubmitting || !hasPublishPermission) { return true; } @@ -59,6 +60,19 @@ class PublishButtonComponent extends Component { return !allCompleted; }; + hasPermission = (permissions) => { + return permissions.can_publish; + }; + + getDisabledButtonPopupText = (hasPublishPermission) => { + let text = i18next.t("Required fields are missing"); + + if (!hasPublishPermission) { + text = i18next.t("You don't have permission to publish"); + } + return text; + }; + render() { const { actionState, @@ -67,6 +81,7 @@ class PublishButtonComponent extends Component { publishWithoutCommunity, formik, publishModalExtraContent, + permissions, ...ui } = this.props; const { isConfirmModalOpen } = this.state; @@ -74,19 +89,38 @@ class PublishButtonComponent extends Component { const uiProps = _omit(ui, ["dispatch"]); + const hasPublishPermission = this.hasPermission(permissions); + const publishDisabled = this.isDisabled( + values, + isSubmitting, + filesState, + hasPublishPermission + ); + + // only used when button is disabled + const popupText = this.getDisabledButtonPopupText(hasPublishPermission); + return ( <> -