diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 908a7c903a5..f1437e358a0 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -208,7 +208,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index aded3a2f01b..10d4722e0f8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -68,7 +68,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: diff --git a/.github/workflows/run-openrewrite.yml b/.github/workflows/run-openrewrite.yml index 66a815f7984..0595413e3f8 100644 --- a/.github/workflows/run-openrewrite.yml +++ b/.github/workflows/run-openrewrite.yml @@ -45,7 +45,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: diff --git a/.github/workflows/tests-code.yml b/.github/workflows/tests-code.yml index 20c37a8a891..56409776a92 100644 --- a/.github/workflows/tests-code.yml +++ b/.github/workflows/tests-code.yml @@ -65,7 +65,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -99,7 +98,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -134,7 +132,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -180,7 +177,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -253,7 +249,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -287,7 +282,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -338,7 +332,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -388,7 +381,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -425,7 +417,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: @@ -489,7 +480,6 @@ jobs: - name: Use cache uses: actions/cache@v4 with: - lookup-only: true path: ~/.jbang key: ${{ steps.cache-key.outputs.cache_key }} restore-keys: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f6ec27d777..c39ddcdbf03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added the field `monthfiled` to the default list of fields to resolve BibTeX-Strings for [#13375](https://github.com/JabRef/jabref/issues/13375) - We added a new ID based fetcher for [EuropePMC](https://europepmc.org/). [#13389](https://github.com/JabRef/jabref/pull/13389) - We added an initial [cite as you write](https://retorque.re/zotero-better-bibtex/citing/cayw/) endpoint. [#13187](https://github.com/JabRef/jabref/issues/13187) +- In case no citation relation information can be fetched, we show the data providers reason. [#13549](https://github.com/JabRef/jabref/pull/13549) ### Changed diff --git a/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java index de428484f29..f29b929ad43 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java @@ -8,7 +8,7 @@ public final class GroupChangeDetailsView extends DatabaseChangeDetailsView { public GroupChangeDetailsView(GroupChange groupChange) { - String labelValue = ""; + String labelValue; if (groupChange.getGroupDiff().getNewGroupRoot() == null) { labelValue = groupChange.getName() + '.'; } else { diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java index 3820886b8c8..49470ccaba3 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java @@ -476,9 +476,9 @@ private void executeSearch(CitationComponents citationComponents) { citationComponents.listView().setItems(observableList); // TODO: It should not be possible to cancel a search task that is already running for same tab - if (citingTask != null && !citingTask.isCancelled() && citationComponents.searchType() == CitationFetcher.SearchType.CITES) { + if (citationComponents.searchType() == CitationFetcher.SearchType.CITES && citingTask != null && !citingTask.isCancelled()) { citingTask.cancel(); - } else if (citedByTask != null && !citedByTask.isCancelled() && citationComponents.searchType() == CitationFetcher.SearchType.CITED_BY) { + } else if (citationComponents.searchType() == CitationFetcher.SearchType.CITED_BY && citedByTask != null && !citedByTask.isCancelled()) { citedByTask.cancel(); } @@ -489,10 +489,17 @@ private void executeSearch(CitationComponents citationComponents) { observableList )) .onFailure(exception -> { - LOGGER.error("Error while fetching citing Articles", exception); + LOGGER.error("Error while fetching {} papers", citationComponents.searchType() == CitationFetcher.SearchType.CITES ? "cited" : "citing", exception); hideNodes(citationComponents.abortButton(), citationComponents.progress(), citationComponents.importButton()); - citationComponents.listView().setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0", - exception.getMessage()))); + String labelText; + if (citationComponents.searchType() == CitationFetcher.SearchType.CITES) { + labelText = Localization.lang("Error while fetching cited entries: %0", exception.getMessage()); + } else { + labelText = Localization.lang("Error while fetching citing entries: %0", exception.getMessage()); + } + Label placeholder = new Label(labelText); + placeholder.setWrapText(true); + citationComponents.listView().setPlaceholder(placeholder); citationComponents.refreshButton().setVisible(true); dialogService.notify(exception.getMessage()); }) @@ -508,13 +515,13 @@ private BackgroundTask> createBackgroundTask( return switch (searchType) { case CitationFetcher.SearchType.CITES -> { citingTask = BackgroundTask.wrap( - () -> this.searchCitationsRelationsService.searchReferences(entry) + () -> this.searchCitationsRelationsService.searchCites(entry) ); yield citingTask; } case CitationFetcher.SearchType.CITED_BY -> { citedByTask = BackgroundTask.wrap( - () -> this.searchCitationsRelationsService.searchCitations(entry) + () -> this.searchCitationsRelationsService.searchCitedBy(entry) ); yield citedByTask; } @@ -527,6 +534,8 @@ private void onSearchForRelationsSucceed(CitationComponents citationComponents, hideNodes(citationComponents.abortButton(), citationComponents.progress()); + // TODO: This could be a wrong database, because the user might have switched to another library + // If we were on fixing this, we would need to a) associate a BibEntry with a database or b) pass the database at "bindToEntry" BibDatabase database = stateManager.getActiveDatabase().map(BibDatabaseContext::getDatabase).orElse(new BibDatabase()); observableList.setAll( fetchedList.stream().map(entr -> @@ -539,11 +548,11 @@ private void onSearchForRelationsSucceed(CitationComponents citationComponents, .toList() ); - if (!observableList.isEmpty()) { - citationComponents.listView().refresh(); - } else { + if (observableList.isEmpty()) { Label placeholder = new Label(Localization.lang("No articles found")); citationComponents.listView().setPlaceholder(placeholder); + } else { + citationComponents.listView().refresh(); } BooleanBinding booleanBind = Bindings.isEmpty(citationComponents.listView().getCheckModel().getCheckedItems()); citationComponents.importButton().disableProperty().bind(booleanBind); diff --git a/jablib/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java b/jablib/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java index fb80968bebc..dbd353b234b 100644 --- a/jablib/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java +++ b/jablib/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java @@ -40,29 +40,23 @@ public SearchCitationsRelationsService(ImporterPreferences importerPreferences, ); } - /** - * @implNote Typically, this would be a Shim in JavaFX - */ @VisibleForTesting - public SearchCitationsRelationsService(CitationFetcher citationFetcher, + SearchCitationsRelationsService(CitationFetcher citationFetcher, BibEntryCitationsAndReferencesRepository repository ) { this.citationFetcher = citationFetcher; this.relationsRepository = repository; } - public List searchReferences(BibEntry referenced) { - boolean isFetchingAllowed = relationsRepository.isReferencesUpdatable(referenced) - || !relationsRepository.containsReferences(referenced); + public List searchCites(BibEntry referencing) throws FetcherException { + boolean isFetchingAllowed = + !relationsRepository.containsReferences(referencing) || + relationsRepository.isReferencesUpdatable(referencing); if (isFetchingAllowed) { - try { - List referencedBy = citationFetcher.searchCiting(referenced); - relationsRepository.insertReferences(referenced, referencedBy); - } catch (FetcherException e) { - LOGGER.error("Error while fetching references for entry {}", referenced.getTitle(), e); - } + List referencedBy = citationFetcher.searchCiting(referencing); + relationsRepository.insertReferences(referencing, referencedBy); } - return relationsRepository.readReferences(referenced); + return relationsRepository.readReferences(referencing); } /** @@ -70,16 +64,13 @@ public List searchReferences(BibEntry referenced) { * If the store was not empty and nothing was fetched after a successful fetch => the store will be erased and the returned collection will be empty * If the store was not empty and an error occurs while fetching => will return the content of the store */ - public List searchCitations(BibEntry cited) { - boolean isFetchingAllowed = relationsRepository.isCitationsUpdatable(cited) - || !relationsRepository.containsCitations(cited); + public List searchCitedBy(BibEntry cited) throws FetcherException { + boolean isFetchingAllowed = + !relationsRepository.containsCitations(cited) || + relationsRepository.isCitationsUpdatable(cited); if (isFetchingAllowed) { - try { - List citedBy = citationFetcher.searchCitedBy(cited); - relationsRepository.insertCitations(cited, citedBy); - } catch (FetcherException e) { - LOGGER.error("Error while fetching citations for entry {}", cited.getTitle(), e); - } + List citedBy = citationFetcher.searchCitedBy(cited); + relationsRepository.insertCitations(cited, citedBy); } return relationsRepository.readCitations(cited); } diff --git a/jablib/src/main/java/org/jabref/logic/importer/fetcher/citation/semanticscholar/SemanticScholarCitationFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/fetcher/citation/semanticscholar/SemanticScholarCitationFetcher.java index de4897e96e4..b3865d04fd3 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fetcher/citation/semanticscholar/SemanticScholarCitationFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fetcher/citation/semanticscholar/SemanticScholarCitationFetcher.java @@ -9,6 +9,7 @@ import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.fetcher.CustomizableKeyFetcher; import org.jabref.logic.importer.fetcher.citation.CitationFetcher; +import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.URLUtil; import org.jabref.model.entry.BibEntry; @@ -17,9 +18,14 @@ import kong.unirest.core.json.JSONObject; import org.jooq.lambda.Unchecked; import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SemanticScholarCitationFetcher implements CitationFetcher, CustomizableKeyFetcher { public static final String FETCHER_NAME = "Semantic Scholar Citations Fetcher"; + + private static final Logger LOGGER = LoggerFactory.getLogger(SemanticScholarCitationFetcher.class); + private static final String SEMANTIC_SCHOLAR_API = "https://api.semanticscholar.org/graph/v1/"; private static final Gson GSON = new Gson(); @@ -30,8 +36,8 @@ public SemanticScholarCitationFetcher(ImporterPreferences importerPreferences) { this.importerPreferences = importerPreferences; } - public String getAPIUrl(String entry_point, BibEntry entry) { - return SEMANTIC_SCHOLAR_API + "paper/" + "DOI:" + entry.getDOI().orElseThrow().asString() + "/" + entry_point + public String getAPIUrl(String entryPoint, BibEntry entry) { + return SEMANTIC_SCHOLAR_API + "paper/" + "DOI:" + entry.getDOI().orElseThrow().asString() + "/" + entryPoint + "?fields=" + "title,authors,year,citationCount,referenceCount,externalIds,publicationTypes,abstract,url" + "&limit=1000"; } @@ -51,6 +57,7 @@ public List searchCitedBy(BibEntry entry) throws FetcherException { URL citationsUrl; try { citationsUrl = URLUtil.create(getAPIUrl("citations", entry)); + LOGGER.debug("Cited URL {} ", citationsUrl); } catch (MalformedURLException e) { throw new FetcherException("Malformed URL", e); } @@ -75,15 +82,29 @@ public List searchCitedBy(BibEntry entry) throws FetcherException { URL referencesUrl; try { referencesUrl = URLUtil.create(getAPIUrl("references", entry)); + LOGGER.debug("Citing URL {} ", referencesUrl); } catch (MalformedURLException e) { throw new FetcherException("Malformed URL", e); } URLDownload urlDownload = new URLDownload(referencesUrl); importerPreferences.getApiKey(getName()).ifPresent(apiKey -> urlDownload.addHeader("x-api-key", apiKey)); - ReferencesResponse referencesResponse = GSON.fromJson(urlDownload.asString(), ReferencesResponse.class); + String response = urlDownload.asString(); + ReferencesResponse referencesResponse = GSON.fromJson(response, ReferencesResponse.class); if (referencesResponse.getData() == null) { + // Get error message from citingPaperInfo.openAccessPdf.disclaimer + JSONObject responseObject = new JSONObject(response); + Optional.ofNullable(responseObject.optJSONObject("citingPaperInfo")) + .flatMap(citingPaperInfo -> Optional.ofNullable(citingPaperInfo.optJSONObject("openAccessPdf"))) + .flatMap(openAccessPdf -> Optional.ofNullable(openAccessPdf.optString("disclaimer"))) + .ifPresent(Unchecked.consumer(disclaimer -> { + LOGGER.debug("Received a disclaimer from Semantic Scholar: {}", disclaimer); + if (disclaimer.contains("references")) { + throw new FetcherException(Localization.lang("Restricted access to references: %0", disclaimer)); + } + } + )); return List.of(); } diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 08f6c795f72..eab77bc5ecf 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -1698,6 +1698,12 @@ Open\ one\ before\ citing.=Open one before citing. Select\ one\ before\ citing.=Select one before citing. Select\ some\ before\ citing.=Select some before citing. +Citation\ relations=Citation relations +Show\ articles\ related\ by\ citation=Show articles related by citation +Error\ while\ fetching\ cited\ entries\:\ %0=Error while fetching cited entries: %0 +Error\ while\ fetching\ citing\ entries\:\ %0=Error while fetching citing entries: %0 +Restricted\ access\ to\ references\:\ %0=Restricted access to references: %0 + Found\ identical\ ranges=Found identical ranges Found\ overlapping\ ranges=Found overlapping ranges Found\ touching\ ranges=Found touching ranges @@ -2812,9 +2818,7 @@ Restart\ search=Restart search Cancel\ search=Cancel search Select\ entry=Select entry Search\ aborted.=Search aborted. -Citation\ relations=Citation relations -Show\ articles\ related\ by\ citation=Show articles related by citation -Error\ while\ fetching\ citing\ entries\:\ %0=Error while fetching citing entries: %0 + Help\ on\ external\ applications=Help on external applications Identifier-based\ Web\ Search=Identifier-based Web Search diff --git a/jablib/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java b/jablib/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java index 41609987871..00ccf3a124e 100644 --- a/jablib/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java +++ b/jablib/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java @@ -60,7 +60,7 @@ private CitationFetcher createEmptyMockFetcher() { @Nested class CitationsTests { @Test - void serviceShouldSearchForCitations() { + void serviceShouldSearchForCitations() throws FetcherException { // GIVEN BibEntry cited = new BibEntry(); List citationsToReturn = List.of(new BibEntry()); @@ -70,14 +70,14 @@ void serviceShouldSearchForCitations() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(null, repository); // WHEN - List citations = searchService.searchCitations(cited); + List citations = searchService.searchCitedBy(cited); // THEN assertEquals(citationsToReturn, citations); } @Test - void serviceShouldCallTheFetcherForCitationsWhenRepositoryIsUpdatable() { + void serviceShouldCallTheFetcherForCitationsWhenRepositoryIsUpdatable() throws FetcherException { // GiVEN BibEntry cited = new BibEntry(); BibEntry newCitations = new BibEntry(); @@ -95,7 +95,7 @@ void serviceShouldCallTheFetcherForCitationsWhenRepositoryIsUpdatable() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(fetcher, repository); // WHEN - List citations = searchService.searchCitations(cited); + List citations = searchService.searchCitedBy(cited); // THEN assertTrue(citationsDatabase.containsKey(cited)); @@ -104,7 +104,7 @@ void serviceShouldCallTheFetcherForCitationsWhenRepositoryIsUpdatable() { } @Test - void serviceShouldFetchCitationsIfRepositoryIsEmpty() { + void serviceShouldFetchCitationsIfRepositoryIsEmpty() throws FetcherException { BibEntry cited = new BibEntry(); BibEntry newCitations = new BibEntry(); List citationsToReturn = List.of(newCitations); @@ -114,7 +114,7 @@ void serviceShouldFetchCitationsIfRepositoryIsEmpty() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(fetcher, repository); // WHEN - List citations = searchService.searchCitations(cited); + List citations = searchService.searchCitedBy(cited); // THEN assertTrue(citationsDatabase.containsKey(cited)); @@ -123,7 +123,7 @@ void serviceShouldFetchCitationsIfRepositoryIsEmpty() { } @Test - void insertingAnEmptyCitationsShouldBePossible() { + void insertingAnEmptyCitationsShouldBePossible() throws FetcherException { BibEntry cited = new BibEntry(); Map> citationsDatabase = new HashMap<>(); CitationFetcher fetcher = createEmptyMockFetcher(); @@ -131,7 +131,7 @@ void insertingAnEmptyCitationsShouldBePossible() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(fetcher, repository); // WHEN - List citations = searchService.searchCitations(cited); + List citations = searchService.searchCitedBy(cited); // THEN assertTrue(citations.isEmpty()); @@ -143,7 +143,7 @@ void insertingAnEmptyCitationsShouldBePossible() { @Nested class ReferencesTests { @Test - void serviceShouldSearchForReferences() { + void serviceShouldSearchForReferences() throws FetcherException { // GIVEN BibEntry referencer = new BibEntry(); List referencesToReturn = List.of(new BibEntry()); @@ -153,14 +153,14 @@ void serviceShouldSearchForReferences() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(null, repository); // WHEN - List references = searchService.searchReferences(referencer); + List references = searchService.searchCites(referencer); // THEN assertEquals(referencesToReturn, references); } @Test - void serviceShouldCallTheFetcherForReferencesWhenRepositoryIsUpdatable() { + void serviceShouldCallTheFetcherForReferencesWhenRepositoryIsUpdatable() throws FetcherException { // GIVEN BibEntry referencer = new BibEntry(); BibEntry newReference = new BibEntry(); @@ -178,7 +178,7 @@ void serviceShouldCallTheFetcherForReferencesWhenRepositoryIsUpdatable() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(fetcher, repository); // WHEN - List references = searchService.searchReferences(referencer); + List references = searchService.searchCites(referencer); // THEN assertTrue(referencesDatabase.containsKey(referencer)); @@ -187,7 +187,7 @@ void serviceShouldCallTheFetcherForReferencesWhenRepositoryIsUpdatable() { } @Test - void serviceShouldFetchReferencesIfRepositoryIsEmpty() { + void serviceShouldFetchReferencesIfRepositoryIsEmpty() throws FetcherException { BibEntry reference = new BibEntry(); BibEntry newCitations = new BibEntry(); List referencesToReturn = List.of(newCitations); @@ -199,7 +199,7 @@ void serviceShouldFetchReferencesIfRepositoryIsEmpty() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(fetcher, repository); // WHEN - List references = searchService.searchReferences(reference); + List references = searchService.searchCites(reference); // THEN assertTrue(referencesDatabase.containsKey(reference)); @@ -208,7 +208,7 @@ void serviceShouldFetchReferencesIfRepositoryIsEmpty() { } @Test - void insertingAnEmptyReferencesShouldBePossible() { + void insertingAnEmptyReferencesShouldBePossible() throws FetcherException { BibEntry referencer = new BibEntry(); Map> referenceDatabase = new HashMap<>(); CitationFetcher fetcher = createEmptyMockFetcher(); @@ -218,7 +218,7 @@ void insertingAnEmptyReferencesShouldBePossible() { SearchCitationsRelationsService searchService = new SearchCitationsRelationsService(fetcher, repository); // WHEN - List citations = searchService.searchReferences(referencer); + List citations = searchService.searchCites(referencer); // THEN assertTrue(citations.isEmpty());