Skip to content

Commit 20874f9

Browse files
Web Import: Add New Entries to 'Imported Entries' Group (#12998)
* Add field to configure imported entries group under web search preference * Add 'Import entries' group feature for web import entries * Add few test case * Add JabRef_en.properties entry and revert access modifier * Fix methods in test * Add CHANGELOG.md entry * Refactor method for storing and setting group name --------- Co-authored-by: Christoph <[email protected]>
1 parent 2841904 commit 20874f9

File tree

18 files changed

+267
-6
lines changed

18 files changed

+267
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
1919
- We added [LOBID](https://lobid.org/) as an alternative ISBN-Fetcher. [#13076](https://github.com/JabRef/jabref/issues/13076)
2020
- We added a success dialog when using the "Copy to" option, indicating whether the entry was successfully copied and specifying if a cross-reference entry was included. [#12486](https://github.com/JabRef/jabref/issues/12486)
2121
- We added a new button to toggle the file path between an absolute and relative formats in context of library properties. [#13031](https://github.com/JabRef/jabref/issues/13031)
22+
- We introduced user-configurable group 'Imported entries' for automatic import of entries from web search, PDF import and web fetchers. [#12548](https://github.com/JabRef/jabref/issues/12548)
2223
- We added automatic selection of the “Enter Identifier” tab with pre-filled clipboard content if the clipboard contains a valid identifier when opening the “Create New Entry” dialog. [#13087](https://github.com/JabRef/jabref/issues/13087)
2324
- We added an "Open example library" button to Welcome Tab. [#13014](https://github.com/JabRef/jabref/issues/13014)
2425
- We added automatic detection and selection of the identifier type (e.g., DOI, ISBN, arXiv) based on clipboard content when opening the "New Entry" dialog [#13111](https://github.com/JabRef/jabref/pull/13111)

jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.jabref.model.entry.field.StandardField;
5757
import org.jabref.model.groups.GroupEntryChanger;
5858
import org.jabref.model.groups.GroupTreeNode;
59+
import org.jabref.model.groups.SmartGroup;
5960
import org.jabref.model.util.FileUpdateMonitor;
6061
import org.jabref.model.util.OptionalUtil;
6162

@@ -164,6 +165,7 @@ public List<ImportFilesResultItemViewModel> call() {
164165
// Modifiers do not work on macOS: https://bugs.openjdk.org/browse/JDK-8264172
165166
// Similar code as org.jabref.gui.preview.PreviewPanel.PreviewPanel
166167
DragDrop.handleDropOfFiles(List.of(file), transferMode, fileLinker, entry);
168+
addToImportEntriesGroup(pdfEntriesInFile);
167169
entriesToAdd.addAll(pdfEntriesInFile);
168170
addResultToList(file, true, Localization.lang("File was successfully imported as a new entry"));
169171
});
@@ -267,6 +269,7 @@ private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext
267269
finalEntry = duplicateHandledEntry.get();
268270
}
269271
importCleanedEntries(bibDatabaseContext, List.of(finalEntry));
272+
addToImportEntriesGroup(List.of(finalEntry));
270273
downloadLinkedFiles(finalEntry);
271274
BibEntry entryToFocus = finalEntry;
272275
stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(entryToFocus));
@@ -510,4 +513,17 @@ private List<BibEntry> handlePdfUrl(String pdfUrl) throws IOException {
510513
return List.of();
511514
}
512515
}
516+
517+
private void addToImportEntriesGroup(List<BibEntry> entriesToInsert) {
518+
if (preferences.getLibraryPreferences().isAddImportedEntriesEnabled()) {
519+
// Only one SmartGroup
520+
this.bibDatabaseContext.getMetaData()
521+
.getGroups()
522+
.flatMap(grp -> grp.getChildren()
523+
.stream()
524+
.filter(node -> node.getGroup() instanceof SmartGroup)
525+
.findFirst())
526+
.ifPresent(smtGrp -> smtGrp.addEntriesToGroup(entriesToInsert));
527+
}
528+
}
513529
}

jabgui/src/main/java/org/jabref/gui/groups/GroupDescriptions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.jabref.model.groups.ExplicitGroup;
55
import org.jabref.model.groups.KeywordGroup;
66
import org.jabref.model.groups.SearchGroup;
7+
import org.jabref.model.groups.SmartGroup;
78
import org.jabref.model.strings.StringUtil;
89

910
public class GroupDescriptions {
@@ -61,6 +62,10 @@ public static String getShortDescriptionAllEntriesGroup() {
6162
return Localization.lang("<b>All Entries</b> (this group cannot be edited or removed)");
6263
}
6364

65+
public static String getShortDescriptionSmartGroup(SmartGroup smartGroup) {
66+
return Localization.lang("<b>Smart Group</b> (Import Entries)");
67+
}
68+
6469
public static String getShortDescription(SearchGroup searchGroup, boolean showDynamic) {
6570
StringBuilder sb = new StringBuilder();
6671
sb.append("<b>");

jabgui/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.jabref.model.groups.LastNameGroup;
4747
import org.jabref.model.groups.RegexKeywordGroup;
4848
import org.jabref.model.groups.SearchGroup;
49+
import org.jabref.model.groups.SmartGroup;
4950
import org.jabref.model.groups.TexGroup;
5051
import org.jabref.model.search.event.IndexAddedOrUpdatedEvent;
5152
import org.jabref.model.search.event.IndexClosedEvent;
@@ -432,6 +433,8 @@ public boolean canAddEntriesIn() {
432433
AbstractGroup group = groupNode.getGroup();
433434
if (group instanceof AllEntriesGroup) {
434435
return false;
436+
} else if (group instanceof SmartGroup) {
437+
return false;
435438
} else if (group instanceof ExplicitGroup) {
436439
return true;
437440
} else if (group instanceof LastNameGroup || group instanceof RegexKeywordGroup) {
@@ -458,7 +461,7 @@ public boolean canAddEntriesIn() {
458461
public boolean canBeDragged() {
459462
AbstractGroup group = groupNode.getGroup();
460463
return switch (group) {
461-
case AllEntriesGroup _ -> false;
464+
case AllEntriesGroup _, SmartGroup _ -> false;
462465
case ExplicitGroup _, SearchGroup _, AutomaticKeywordGroup _, AutomaticPersonsGroup _, TexGroup _ -> true;
463466
case KeywordGroup _ ->
464467
// KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup
@@ -477,13 +480,13 @@ public boolean canAddGroupsIn() {
477480
AbstractGroup group = groupNode.getGroup();
478481
return switch (group) {
479482
case AllEntriesGroup _, ExplicitGroup _, SearchGroup _, TexGroup _ -> true;
483+
case AutomaticKeywordGroup _, AutomaticPersonsGroup _, SmartGroup _ -> false;
480484
case KeywordGroup _ ->
481485
// KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup
482486
groupNode.getParent()
483487
.map(GroupTreeNode::getGroup)
484488
.map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup))
485489
.orElse(false);
486-
case AutomaticKeywordGroup _, AutomaticPersonsGroup _ -> false;
487490
case null -> throw new IllegalArgumentException("Group cannot be null");
488491
default -> throw new UnsupportedOperationException("canAddGroupsIn method not yet implemented in group: " + group.getClass().getName());
489492
};
@@ -492,7 +495,7 @@ public boolean canAddGroupsIn() {
492495
public boolean canRemove() {
493496
AbstractGroup group = groupNode.getGroup();
494497
return switch (group) {
495-
case AllEntriesGroup _ -> false;
498+
case AllEntriesGroup _, SmartGroup _ -> false;
496499
case ExplicitGroup _, SearchGroup _, AutomaticKeywordGroup _, AutomaticPersonsGroup _, TexGroup _ -> true;
497500
case KeywordGroup _ ->
498501
// KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup
@@ -508,7 +511,7 @@ public boolean canRemove() {
508511
public boolean isEditable() {
509512
AbstractGroup group = groupNode.getGroup();
510513
return switch (group) {
511-
case AllEntriesGroup _ -> false;
514+
case AllEntriesGroup _, SmartGroup _ -> false;
512515
case ExplicitGroup _, SearchGroup _, AutomaticKeywordGroup _, AutomaticPersonsGroup _, TexGroup _ -> true;
513516
case KeywordGroup _ ->
514517
// KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup

jabgui/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.jabref.model.groups.GroupTreeNode;
1818
import org.jabref.model.groups.KeywordGroup;
1919
import org.jabref.model.groups.SearchGroup;
20+
import org.jabref.model.groups.SmartGroup;
2021

2122
public class GroupTreeNodeViewModel {
2223
private final GroupTreeNode node;
@@ -51,6 +52,8 @@ public String getDescription() {
5152
String shortDescription = "";
5253
boolean showDynamic = true;
5354
shortDescription = switch (group) {
55+
case SmartGroup smartGroup ->
56+
GroupDescriptions.getShortDescriptionSmartGroup(smartGroup);
5457
case ExplicitGroup explicitGroup ->
5558
GroupDescriptions.getShortDescriptionExplicitGroup(explicitGroup);
5659
case KeywordGroup keywordGroup ->

jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
import org.jabref.model.groups.AutomaticKeywordGroup;
3838
import org.jabref.model.groups.AutomaticPersonsGroup;
3939
import org.jabref.model.groups.ExplicitGroup;
40+
import org.jabref.model.groups.GroupHierarchyType;
4041
import org.jabref.model.groups.GroupTreeNode;
4142
import org.jabref.model.groups.RegexKeywordGroup;
4243
import org.jabref.model.groups.SearchGroup;
44+
import org.jabref.model.groups.SmartGroup;
4345
import org.jabref.model.groups.TexGroup;
4446
import org.jabref.model.groups.WordKeywordGroup;
4547
import org.jabref.model.metadata.MetaData;
@@ -176,6 +178,29 @@ private void onActiveDatabaseChanged(Optional<BibDatabaseContext> newDatabase) {
176178
rootGroup.setValue(null);
177179
}
178180
currentDatabase = newDatabase;
181+
newDatabase.ifPresent(db -> addGroupImportEntries(rootGroup.get()));
182+
}
183+
184+
private void addGroupImportEntries(GroupNodeViewModel parent) {
185+
if (!preferences.getLibraryPreferences().isAddImportedEntriesEnabled()) {
186+
return;
187+
}
188+
189+
String grpName = preferences.getLibraryPreferences().getAddImportedEntriesGroupName();
190+
AbstractGroup importEntriesGroup = new SmartGroup(grpName, GroupHierarchyType.INDEPENDENT, ',');
191+
boolean isGrpExist = parent.getGroupNode()
192+
.getChildren()
193+
.stream()
194+
.map(GroupTreeNode::getGroup)
195+
.anyMatch(grp -> grp instanceof SmartGroup);
196+
if (!isGrpExist) {
197+
currentDatabase.ifPresent(db -> {
198+
GroupTreeNode newSubgroup = parent.addSubgroup(importEntriesGroup);
199+
newSubgroup.moveTo(parent.getGroupNode(), 0);
200+
selectedGroups.setAll(new GroupNodeViewModel(db, stateManager, taskExecutor, newSubgroup, localDragboard, preferences));
201+
writeGroupChangesToMetaData();
202+
});
203+
}
179204
}
180205

181206
/**

jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public class WebSearchTab extends AbstractPreferenceTabView<WebSearchTabViewMode
3333
@FXML private CheckBox warnAboutDuplicatesOnImport;
3434
@FXML private CheckBox downloadLinkedOnlineFiles;
3535
@FXML private CheckBox keepDownloadUrl;
36+
@FXML private CheckBox addImportedEntries;
37+
@FXML private TextField addImportedEntriesGroupName;
3638
@FXML private ComboBox<PlainCitationParserChoice> defaultPlainCitationParser;
3739

3840
@FXML private CheckBox useCustomDOI;
@@ -76,6 +78,10 @@ public void initialize() {
7678
downloadLinkedOnlineFiles.selectedProperty().bindBidirectional(viewModel.shouldDownloadLinkedOnlineFiles());
7779
keepDownloadUrl.selectedProperty().bindBidirectional(viewModel.shouldKeepDownloadUrl());
7880

81+
addImportedEntries.selectedProperty().bindBidirectional(viewModel.getAddImportedEntries());
82+
addImportedEntriesGroupName.textProperty().bindBidirectional(viewModel.getAddImportedEntriesGroupName());
83+
addImportedEntriesGroupName.disableProperty().bind(addImportedEntries.selectedProperty().not());
84+
7985
new ViewModelListCellFactory<PlainCitationParserChoice>()
8086
.withText(PlainCitationParserChoice::getLocalizedName)
8187
.install(defaultPlainCitationParser);

jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.jabref.gui.preferences.PreferenceTabViewModel;
2222
import org.jabref.gui.slr.StudyCatalogItem;
2323
import org.jabref.logic.FilePreferences;
24+
import org.jabref.logic.LibraryPreferences;
2425
import org.jabref.logic.importer.ImportFormatPreferences;
2526
import org.jabref.logic.importer.ImporterPreferences;
2627
import org.jabref.logic.importer.SearchBasedFetcher;
@@ -48,6 +49,9 @@ public class WebSearchTabViewModel implements PreferenceTabViewModel {
4849
new SimpleListProperty<>(FXCollections.observableArrayList(PlainCitationParserChoice.values()));
4950
private final ObjectProperty<PlainCitationParserChoice> defaultPlainCitationParser = new SimpleObjectProperty<>();
5051

52+
private final BooleanProperty addImportedEntries = new SimpleBooleanProperty();
53+
private final StringProperty addImportedEntriesGroupName = new SimpleStringProperty("");
54+
5155
private final BooleanProperty useCustomDOIProperty = new SimpleBooleanProperty();
5256
private final StringProperty useCustomDOINameProperty = new SimpleStringProperty("");
5357

@@ -67,6 +71,7 @@ public class WebSearchTabViewModel implements PreferenceTabViewModel {
6771
private final ImporterPreferences importerPreferences;
6872
private final FilePreferences filePreferences;
6973
private final ImportFormatPreferences importFormatPreferences;
74+
private final LibraryPreferences libraryPreferences;
7075

7176
private final ReadOnlyBooleanProperty refAiEnabled;
7277

@@ -78,6 +83,7 @@ public WebSearchTabViewModel(CliPreferences preferences, DialogService dialogSer
7883
this.doiPreferences = preferences.getDOIPreferences();
7984
this.filePreferences = preferences.getFilePreferences();
8085
this.importFormatPreferences = preferences.getImportFormatPreferences();
86+
this.libraryPreferences = preferences.getLibraryPreferences();
8187

8288
this.refAiEnabled = refAiEnabled;
8389

@@ -128,6 +134,8 @@ public void setValues() {
128134
warnAboutDuplicatesOnImportProperty.setValue(importerPreferences.shouldWarnAboutDuplicatesOnImport());
129135
shouldDownloadLinkedOnlineFiles.setValue(filePreferences.shouldDownloadLinkedFiles());
130136
shouldkeepDownloadUrl.setValue(filePreferences.shouldKeepDownloadUrl());
137+
addImportedEntries.setValue(libraryPreferences.isAddImportedEntriesEnabled());
138+
addImportedEntriesGroupName.setValue(libraryPreferences.getAddImportedEntriesGroupName());
131139
defaultPlainCitationParser.setValue(importerPreferences.getDefaultPlainCitationParser());
132140

133141
useCustomDOIProperty.setValue(doiPreferences.isUseCustom());
@@ -159,7 +167,14 @@ public void storeSettings() {
159167
importerPreferences.setWarnAboutDuplicatesOnImport(warnAboutDuplicatesOnImportProperty.getValue());
160168
filePreferences.setDownloadLinkedFiles(shouldDownloadLinkedOnlineFiles.getValue());
161169
filePreferences.setKeepDownloadUrl(shouldkeepDownloadUrl.getValue());
170+
libraryPreferences.setAddImportedEntries(addImportedEntries.getValue());
171+
if (addImportedEntriesGroupName.getValue().isEmpty() || addImportedEntriesGroupName.getValue().startsWith(" ")) {
172+
libraryPreferences.setAddImportedEntriesGroupName(Localization.lang("Imported entries"));
173+
} else {
174+
libraryPreferences.setAddImportedEntriesGroupName(addImportedEntriesGroupName.getValue());
175+
}
162176
importerPreferences.setDefaultPlainCitationParser(defaultPlainCitationParser.getValue());
177+
163178
grobidPreferences.setGrobidEnabled(grobidEnabledProperty.getValue());
164179
grobidPreferences.setGrobidUseAsked(grobidPreferences.isGrobidUseAsked());
165180
grobidPreferences.setGrobidURL(grobidURLProperty.getValue());
@@ -189,6 +204,14 @@ public ObjectProperty<PlainCitationParserChoice> defaultPlainCitationParserPrope
189204
return defaultPlainCitationParser;
190205
}
191206

207+
public BooleanProperty getAddImportedEntries() {
208+
return addImportedEntries;
209+
}
210+
211+
public StringProperty getAddImportedEntriesGroupName() {
212+
return addImportedEntriesGroupName;
213+
}
214+
192215
public BooleanProperty useCustomDOIProperty() {
193216
return this.useCustomDOIProperty;
194217
}

jabgui/src/main/resources/org/jabref/gui/preferences/websearch/WebSearchTab.fxml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
<CheckBox fx:id="warnAboutDuplicatesOnImport" text="%Warn about duplicates on import"/>
2222
<CheckBox fx:id="downloadLinkedOnlineFiles" text="%Download referenced files (PDFs, ...)"/>
2323
<CheckBox fx:id="keepDownloadUrl" text="%Store url for downloaded file" />
24+
<HBox alignment="CENTER_LEFT" spacing="10.0">
25+
<CheckBox fx:id="addImportedEntries" text="%Add imported entries to group"/>
26+
<TextField fx:id="addImportedEntriesGroupName" HBox.hgrow="ALWAYS"/>
27+
</HBox>
2428
<HBox alignment="BASELINE_LEFT" spacing="10">
2529
<Label text="%Default plain citation parser"/>
2630
<ComboBox fx:id="defaultPlainCitationParser" HBox.hgrow="ALWAYS"/>

jabgui/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package org.jabref.gui.groups;
22

33
import java.util.EnumSet;
4+
import java.util.List;
45
import java.util.Optional;
56

67
import org.jabref.gui.DialogService;
78
import org.jabref.gui.StateManager;
89
import org.jabref.gui.entryeditor.AdaptVisibleTabs;
910
import org.jabref.gui.preferences.GuiPreferences;
1011
import org.jabref.gui.util.CustomLocalDragboard;
12+
import org.jabref.logic.LibraryPreferences;
1113
import org.jabref.logic.ai.AiService;
1214
import org.jabref.logic.util.CurrentThreadTaskExecutor;
1315
import org.jabref.logic.util.TaskExecutor;
@@ -48,6 +50,13 @@ void setUp() {
4850
preferences = mock(GuiPreferences.class);
4951
dialogService = mock(DialogService.class, Answers.RETURNS_DEEP_STUBS);
5052

53+
when(preferences.getLibraryPreferences()).thenReturn(new LibraryPreferences(
54+
databaseContext.getMode(),
55+
false,
56+
false,
57+
false,
58+
"Imported entries"
59+
));
5160
when(preferences.getGroupsPreferences()).thenReturn(new GroupsPreferences(
5261
EnumSet.noneOf(GroupViewMode.class),
5362
true,
@@ -185,4 +194,27 @@ void shouldNotAddSuggestedGroupsWhenAllExist() {
185194
assertEquals(2, rootGroup.getChildren().size());
186195
assertTrue(rootGroup.hasAllSuggestedGroups());
187196
}
197+
198+
@Test
199+
void shouldCreateImportedEntriesGroupWhenEnabled() {
200+
preferences.getLibraryPreferences().setAddImportedEntries(true);
201+
202+
List<GroupNodeViewModel> prevModel = groupTree.rootGroupProperty().getValue().getChildren();
203+
GroupTreeViewModel model = new GroupTreeViewModel(stateManager, dialogService, mock(AiService.class), preferences, mock(AdaptVisibleTabs.class), taskExecutor, new CustomLocalDragboard());
204+
String actualGrpName = model.rootGroupProperty().getValue().getChildren().getFirst().getDisplayName();
205+
206+
assertEquals(0, prevModel.size());
207+
assertEquals("Imported entries", actualGrpName);
208+
}
209+
210+
@Test
211+
void shouldReflectUpdatedNameForImportedEntriesGroup() {
212+
preferences.getLibraryPreferences().setAddImportedEntries(true);
213+
preferences.getLibraryPreferences().setAddImportedEntriesGroupName("Review list");
214+
215+
GroupTreeViewModel model = new GroupTreeViewModel(stateManager, dialogService, mock(AiService.class), preferences, mock(AdaptVisibleTabs.class), taskExecutor, new CustomLocalDragboard());
216+
String actualGrpName = model.rootGroupProperty().getValue().getChildren().getFirst().getDisplayName();
217+
218+
assertEquals("Review list", actualGrpName);
219+
}
188220
}

0 commit comments

Comments
 (0)