diff --git a/CHANGELOG.md b/CHANGELOG.md index bca01e2d50f..c461952c966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - For automatically created groups, added ability to filter groups by entry type. [#4539](https://github.com/JabRef/jabref/issues/4539) - We added the ability to add field names from the Preferences Dialog [#4546](https://github.com/JabRef/jabref/issues/4546) - We added the ability change the column widths directly in the main table. [#4546](https://github.com/JabRef/jabref/issues/4546) +- We added description of how recommendations where chosen and better error handling to Related Articles tab - We added the ability to execute default action in dialog by using with Ctrl + Enter combination [#4496](https://github.com/JabRef/jabref/issues/4496) - We grouped and reordered the Main Menu (File, Edit, Library, Quality, Tools, and View tabs & icons). [#4666](https://github.com/JabRef/jabref/issues/4666) [#4667](https://github.com/JabRef/jabref/issues/4667) [#4668](https://github.com/JabRef/jabref/issues/4668) [#4669](https://github.com/JabRef/jabref/issues/4669) [#4670](https://github.com/JabRef/jabref/issues/4670) [#4671](https://github.com/JabRef/jabref/issues/4671) [#4672](https://github.com/JabRef/jabref/issues/4672) [#4673](https://github.com/JabRef/jabref/issues/4673) - We added additional modifiers (capitalize, titlecase and sentencecase) to the Bibtex key generator. [#1506](https://github.com/JabRef/jabref/issues/1506) diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css index 5d7c406cb2f..799644a9839 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css @@ -102,10 +102,23 @@ -fx-padding: 20 20 20 20; } +.recommendation-heading { + -fx-font-size: 14pt; + -fx-font-weight: bold; +} + +.recommendation-description { + -fx-font-style: italic; +} + +.recommendation-item { + -fx-padding: 0 0 0 20; +} + #bibtexSourceCodeArea .search { -fx-fill: red; } .bibtexSourceCodeArea .text { -fx-fill: -fx-text-background-color; -} \ No newline at end of file +} diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 72fdf53f104..b7a875655e8 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -284,7 +284,7 @@ private List createTabs() { // Special tabs tabs.add(new MathSciNetTab()); tabs.add(new FileAnnotationTab(panel.getAnnotationCache())); - tabs.add(new RelatedArticlesTab(preferences, dialogService)); + tabs.add(new RelatedArticlesTab(this, preferences, dialogService)); // Source tab sourceTab = new SourceTab(databaseContext, undoManager, preferences.getLatexFieldFormatterPreferences(), preferences.getImportFormatPreferences(), fileMonitor, dialogService, stateManager); diff --git a/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java b/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java index 4bd30b7c614..0fbb3d2e08d 100644 --- a/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java @@ -36,11 +36,13 @@ public class RelatedArticlesTab extends EntryEditorTab { private static final Logger LOGGER = LoggerFactory.getLogger(RelatedArticlesTab.class); private final EntryEditorPreferences preferences; + private final EntryEditor entryEditor; private final DialogService dialogService; - public RelatedArticlesTab(EntryEditorPreferences preferences, DialogService dialogService) { + public RelatedArticlesTab(EntryEditor entryEditor, EntryEditorPreferences preferences, DialogService dialogService) { setText(Localization.lang("Related articles")); setTooltip(new Tooltip(Localization.lang("Related articles"))); + this.entryEditor = entryEditor; this.preferences = preferences; this.dialogService = dialogService; } @@ -63,7 +65,12 @@ private StackPane getRelatedArticlesPane(BibEntry entry) { .onRunning(() -> progress.setVisible(true)) .onSuccess(relatedArticles -> { progress.setVisible(false); - root.getChildren().add(getRelatedArticleInfo(relatedArticles)); + root.getChildren().add(getRelatedArticleInfo(relatedArticles, fetcher)); + }) + .onFailure(exception -> { + LOGGER.error("Error while fetching from Mr. DLib", exception); + progress.setVisible(false); + root.getChildren().add(getErrorInfo()); }) .executeWith(Globals.TASK_EXECUTOR); @@ -77,13 +84,25 @@ private StackPane getRelatedArticlesPane(BibEntry entry) { * @param list List of BibEntries of related articles * @return VBox of related article descriptions to be displayed in the Related Articles tab */ - private VBox getRelatedArticleInfo(List list) { + private ScrollPane getRelatedArticleInfo(List list, MrDLibFetcher fetcher) { + ScrollPane scrollPane = new ScrollPane(); + VBox vBox = new VBox(); vBox.setSpacing(20.0); + String heading = fetcher.getHeading(); + Text headingText = new Text(heading); + headingText.getStyleClass().add("recommendation-heading"); + String description = fetcher.getDescription(); + Text descriptionText = new Text(description); + descriptionText.getStyleClass().add("recommendation-description"); + vBox.getChildren().add(headingText); + vBox.getChildren().add(descriptionText); + for (BibEntry entry : list) { HBox hBox = new HBox(); hBox.setSpacing(5.0); + hBox.getStyleClass().add("recommendation-item"); String title = entry.getTitle().orElse(""); String journal = entry.getField(FieldName.JOURNAL).orElse(""); @@ -109,7 +128,26 @@ private VBox getRelatedArticleInfo(List list) { hBox.getChildren().addAll(titleLink, journalText, authorsText, yearText); vBox.getChildren().add(hBox); } - return vBox; + scrollPane.setContent(vBox); + return scrollPane; + } + + /** + * Gets a ScrollPane to display error info when recommendations fail. + * @return ScrollPane to display in place of recommendations + */ + private ScrollPane getErrorInfo() { + ScrollPane scrollPane = new ScrollPane(); + + VBox vBox = new VBox(); + vBox.setSpacing(20.0); + + Text descriptionText = new Text(Localization.lang("No recommendations received from Mr. DLib for this entry.")); + descriptionText.getStyleClass().add("recommendation-description"); + vBox.getChildren().add(descriptionText); + scrollPane.setContent(vBox); + + return scrollPane; } /** diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index 0e3e30898bd..f09f032cfbb 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -12,6 +12,7 @@ import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.MrDLibImporter; +import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.Version; import org.jabref.model.database.BibDatabase; @@ -30,8 +31,13 @@ public class MrDLibFetcher implements EntryBasedFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(MrDLibFetcher.class); private static final String NAME = "MDL_FETCHER"; private static final String MDL_JABREF_PARTNER_ID = "1"; + private static final String MDL_URL = "api.mr-dlib.org"; + private static final String DEFAULT_MRDLIB_ERROR_MESSAGE = Localization.lang("Error while fetching recommendations from Mr.DLib."); private final String LANGUAGE; private final Version VERSION; + private String heading; + private String description; + private String recommendationSetId; public MrDLibFetcher(String language, Version version) { @@ -54,13 +60,13 @@ public List performSearch(BibEntry entry) throws FetcherException { try { if (importer.isRecognizedFormat(response)) { parserResult = importer.importDatabase(response); + heading = importer.getRecommendationsHeading(); + description = importer.getRecommendationsDescription(); + recommendationSetId = importer.getRecommendationSetId(); } else { // For displaying An ErrorMessage - String error = importer.getResponseErrorMessage(response); - BibEntry errorBibEntry = new BibEntry(); - errorBibEntry.setField("html_representation", error); + description = DEFAULT_MRDLIB_ERROR_MESSAGE; BibDatabase errorBibDataBase = new BibDatabase(); - errorBibDataBase.insertEntry(errorBibEntry); parserResult = new ParserResult(errorBibDataBase); } } catch (IOException e) { @@ -74,6 +80,14 @@ public List performSearch(BibEntry entry) throws FetcherException { } } + public String getHeading() { + return heading; + } + + public String getDescription() { + return description; + } + /** * Contact the server with the title of the selected item * @@ -106,7 +120,7 @@ private String constructQuery(String queryWithTitle) { queryWithTitle = queryWithTitle.replaceAll("/", " "); URIBuilder builder = new URIBuilder(); builder.setScheme("http"); - builder.setHost(getMdlUrl()); + builder.setHost(MDL_URL); builder.setPath("/v2/documents/" + queryWithTitle + "/related_documents"); builder.addParameter("partner_id", MDL_JABREF_PARTNER_ID); builder.addParameter("app_id", "jabref_desktop"); @@ -132,8 +146,4 @@ private String constructQuery(String queryWithTitle) { } return ""; } - - private String getMdlUrl() { - return VERSION.isDevelopmentVersion() ? "api-dev.darwingoliath.com" : "api.mr-dlib.org"; - } } diff --git a/src/main/java/org/jabref/logic/importer/fileformat/MrDLibImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/MrDLibImporter.java index 5bc8af04b8c..b71f5eabf2c 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/MrDLibImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/MrDLibImporter.java @@ -12,13 +12,11 @@ import org.jabref.logic.importer.Importer; import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -29,9 +27,11 @@ */ public class MrDLibImporter extends Importer { - private static final String DEFAULT_MRDLIB_ERROR_MESSAGE = Localization.lang("Error while fetching from Mr.DLib."); private static final Logger LOGGER = LoggerFactory.getLogger(MrDLibImporter.class); public ParserResult parserResult; + private String recommendationsHeading; + private String recommendationsDescription; + private String recommendationSetId; @SuppressWarnings("unused") @Override @@ -111,12 +111,13 @@ private void parse(BufferedReader input) throws IOException { // The Bibdatabase that gets returned in the ParserResult. BibDatabase bibDatabase = new BibDatabase(); // The document to parse - String recommendations = convertToString(input); + String recommendationSet = convertToString(input); + JSONObject recommendationSetJson = new JSONObject(recommendationSet); // The sorted BibEntries gets stored here later List rankedBibEntries = new ArrayList<>(); // Get recommendations from response and populate bib entries - JSONObject recommendationsJson = new JSONObject(recommendations).getJSONObject("recommendations"); + JSONObject recommendationsJson = recommendationSetJson.getJSONObject("recommendations"); Iterator keys = recommendationsJson.keys(); while (keys.hasNext()) { String key = keys.next(); @@ -133,6 +134,11 @@ private void parse(BufferedReader input) throws IOException { bibDatabase.insertEntry(bibentry); } parserResult = new ParserResult(bibDatabase); + + JSONObject label = recommendationSetJson.getJSONObject("label"); + recommendationsHeading = label.getString("label-text"); + recommendationsDescription = label.getString("label-description"); + recommendationSetId = recommendationSetJson.getBigInteger("recommendation_set_id").toString(); } /** @@ -144,7 +150,7 @@ private RankedBibEntry populateBibEntry(JSONObject recommendation) { BibEntry current = new BibEntry(); // parse each of the relevant fields into variables - String authors = isRecommendationFieldPresent(recommendation, "authors") ? getAuthorsString(recommendation) : ""; + String authors = isRecommendationFieldPresent(recommendation, "authors") ? recommendation.getString("authors") : ""; String title = isRecommendationFieldPresent(recommendation, "title") ? recommendation.getString("title") : ""; String year = isRecommendationFieldPresent(recommendation, "published_year") ? Integer.toString(recommendation.getInt("published_year")) : ""; String journal = isRecommendationFieldPresent(recommendation, "published_in") ? recommendation.getString("published_in") : ""; @@ -165,43 +171,20 @@ private Boolean isRecommendationFieldPresent(JSONObject recommendation, String f return recommendation.has(field) && !recommendation.isNull(field); } - /** - * Creates an authors string from a JSON recommendation - * @param recommendation JSON Object recommendation from Mr. DLib - * @return A string of all authors, separated by commas and finished with a full stop. - */ - private String getAuthorsString(JSONObject recommendation) { - String authorsString = ""; - JSONArray array = recommendation.getJSONArray("authors"); - for (int i = 0; i < array.length(); ++i) { - authorsString += array.getString(i) + "; "; - } - int stringLength = authorsString.length(); - if (stringLength > 2) { - authorsString = authorsString.substring(0, stringLength - 2) + "."; - } - return authorsString; - } - public ParserResult getParserResult() { return parserResult; } - /** - * Gets the error message to be returned if there has been an error in returning recommendations. - * Returns default error message if there is no message from Mr. DLib. - * @param response The response from the MDL server as a string. - * @return String error message to be shown to the user. - */ - public String getResponseErrorMessage(String response) { - try { - JSONObject jsonObject = new JSONObject(response); - if (!jsonObject.has("message")) { - return jsonObject.getString("message"); - } - } catch (JSONException ex) { - return DEFAULT_MRDLIB_ERROR_MESSAGE; - } - return DEFAULT_MRDLIB_ERROR_MESSAGE; + public String getRecommendationsHeading() { + return recommendationsHeading; } + + public String getRecommendationsDescription() { + return recommendationsDescription; + } + + public String getRecommendationSetId() { + return recommendationSetId; + } + } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index bae2e82011d..20ed0a4cd14 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -334,8 +334,6 @@ Error\ occurred\ when\ parsing\ entry=Error occurred when parsing entry Error\ opening\ file=Error opening file -Error\ while\ fetching\ from\ Mr.DLib.=Error while fetching from Mr.DLib. - Error\ while\ writing=Error while writing '%0'\ exists.\ Overwrite\ file?='%0' exists. Overwrite file? @@ -570,6 +568,10 @@ Move\ up=Move up Moved\ group\ "%0".=Moved group "%0". +No\ recommendations\ received\ from\ Mr.\ DLib\ for\ this\ entry.=No recommendations received from Mr. DLib for this entry. + +Error\ while\ fetching\ recommendations\ from\ Mr.DLib.=Error while fetching recommendations from Mr.DLib. + Name=Name Name\ formatter=Name formatter diff --git a/src/test/java/org/jabref/logic/importer/fileformat/MrDLibImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/MrDLibImporterTest.java index 723afd1b6fc..246bee9e3cf 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/MrDLibImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/MrDLibImporterTest.java @@ -24,7 +24,7 @@ public class MrDLibImporterTest { @BeforeEach public void setUp() { importer = new MrDLibImporter(); - String testInput = "{ \"label\": { \"label-language\": \"en\", \"label-text\": \"Related Items\" }, \"recommendation-set-id\": \"1\", \"recommendations\": { \"74021358\": { \"abstract\": \"abstract\", \"authors\": [ \"Sajovic, Marija\" ], \"published_year\": \"2006\", \"item_id_original\": \"12088644\", \"keywords\": [ \"visoko\\u0161olski program Geodezija - smer Prostorska informatika\" ], \"language_provided\": \"sl\", \"recommendation_id\": \"1\", \"title\": \"The protection of rural lands with the spatial development strategy on the case of Hrastnik commune\", \"url\": \"http://drugg.fgg.uni-lj.si/701/1/GEV_0199_Sajovic.pdf\" }, \"82005804\": { \"abstract\": \"abstract\", \"year_published\": null, \"item_id_original\": \"30145702\", \"language_provided\": null, \"recommendation_id\": \"2\", \"title\": \"Engagement of the volunteers in the solution to the accidents in the South-Moravia region\" }, \"82149599\": { \"abstract\": \"abstract\", \"year_published\": null, \"item_id_original\": \"97690763\", \"language_provided\": null, \"recommendation_id\": \"3\", \"title\": \"\\\"The only Father's word\\\". The relationship of the Father and the Son in the documents of saint John of the Cross\", \"url\": \"http://www.nusl.cz/ntk/nusl-285711\" }, \"84863921\": { \"abstract\": \"abstract\", \"authors\": [ \"Kaffa, Elena\" ], \"year_published\": null, \"item_id_original\": \"19397104\", \"keywords\": [ \"BX\", \"D111\" ], \"language_provided\": \"en\", \"recommendation_id\": \"4\", \"title\": \"Greek Church of Cyprus, the Morea and Constantinople during the Frankish Era (1196-1303)\" }, \"88950992\": { \"abstract\": \"abstract\", \"authors\": [ \"Yasui, Kono\" ], \"year_published\": null, \"item_id_original\": \"38763657\", \"language_provided\": null, \"recommendation_id\": \"5\", \"title\": \"A Phylogenetic Consideration on the Vascular Plants, Cotyledonary Node Including Hypocotyl Being Taken as the Ancestral Form : A Preliminary Note\" } }}"; + String testInput = "{\"label\": {\"label-description\": \"The following articles are similar to the document have currently selected.\", \"label-language\": \"en\", \"label-text\": \"Related Articles\"}, \"recommendation_set_id\": \"1\", \"recommendations\": { \"74021358\": { \"abstract\": \"abstract\", \"authors\":\"Sajovic, Marija\", \"published_year\": \"2006\", \"item_id_original\": \"12088644\", \"keywords\": [ \"visoko\\u0161olski program Geodezija - smer Prostorska informatika\" ], \"language_provided\": \"sl\", \"recommendation_id\": \"1\", \"title\": \"The protection of rural lands with the spatial development strategy on the case of Hrastnik commune\", \"url\": \"http://drugg.fgg.uni-lj.si/701/1/GEV_0199_Sajovic.pdf\" }, \"82005804\": { \"abstract\": \"abstract\", \"year_published\": null, \"item_id_original\": \"30145702\", \"language_provided\": null, \"recommendation_id\": \"2\", \"title\": \"Engagement of the volunteers in the solution to the accidents in the South-Moravia region\" }, \"82149599\": { \"abstract\": \"abstract\", \"year_published\": null, \"item_id_original\": \"97690763\", \"language_provided\": null, \"recommendation_id\": \"3\", \"title\": \"\\\"The only Father's word\\\". The relationship of the Father and the Son in the documents of saint John of the Cross\", \"url\": \"http://www.nusl.cz/ntk/nusl-285711\" }, \"84863921\": { \"abstract\": \"abstract\", \"authors\":\"Kaffa, Elena\", \"year_published\": null, \"item_id_original\": \"19397104\", \"keywords\": [ \"BX\", \"D111\" ], \"language_provided\": \"en\", \"recommendation_id\": \"4\", \"title\": \"Greek Church of Cyprus, the Morea and Constantinople during the Frankish Era (1196-1303)\" }, \"88950992\": { \"abstract\": \"abstract\", \"authors\":\"Yasui, Kono\", \"year_published\": null, \"item_id_original\": \"38763657\", \"language_provided\": null, \"recommendation_id\": \"5\", \"title\": \"A Phylogenetic Consideration on the Vascular Plants, Cotyledonary Node Including Hypocotyl Being Taken as the Ancestral Form : A Preliminary Note\" } }}"; input = new BufferedReader(new StringReader(testInput)); }