Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue #10418 - Added the ability to focus on the most viewed entries #12077

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added automatic browser extension install on Windows for Chrome and Edge. [#6076](https://github.com/JabRef/jabref/issues/6076)
- We added a search bar for filtering keyboard shortcuts. [#11686](https://github.com/JabRef/jabref/issues/11686)
- By double clicking on a local citation in the Citation Relations Tab you can now jump the linked entry. [#11955](https://github.com/JabRef/jabref/pull/11955)
- Added the ability to group entries by popularity over a given time period [#10418](https://github.com/JabRef/jabref/issues/10418)

### Changed

Expand Down
34 changes: 34 additions & 0 deletions src/main/java/org/jabref/gui/LibraryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this useful for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that, I was having issues with file locks earlier in development and forgot to remove the import and some variables I never used.

import java.util.function.Supplier;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -44,6 +45,7 @@
import org.jabref.gui.autosaveandbackup.AutosaveManager;
import org.jabref.gui.autosaveandbackup.BackupManager;
import org.jabref.gui.collab.DatabaseChangeMonitor;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.dialogs.AutosaveUiManager;
import org.jabref.gui.entryeditor.EntryEditor;
import org.jabref.gui.exporter.SaveDatabaseAction;
Expand All @@ -55,6 +57,7 @@
import org.jabref.gui.maintable.MainTable;
import org.jabref.gui.maintable.MainTableDataModel;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.preferences.entry.EntryTabViewModel;
import org.jabref.gui.undo.CountingUndoManager;
import org.jabref.gui.undo.NamedCompound;
import org.jabref.gui.undo.RedoAction;
Expand Down Expand Up @@ -94,6 +97,7 @@
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.groups.PopularityGroup;
import org.jabref.model.search.query.SearchQuery;
import org.jabref.model.util.DirectoryMonitor;
import org.jabref.model.util.DirectoryMonitorManager;
Expand All @@ -105,6 +109,8 @@
import com.tobiasdiez.easybind.Subscription;
import org.controlsfx.control.NotificationPane;
import org.controlsfx.control.action.Action;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -151,6 +157,10 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR }

private SuggestionProviders suggestionProviders;

// Used to track how many views each attachment has.
private final Path viewStorePath = NativeDesktop.getOtherDataDir().resolve("tracking.mv");
private final ReentrantLock fileLock = new ReentrantLock();

@SuppressWarnings({"FieldCanBeLocal"})
private Subscription dividerPositionSubscription;

Expand Down Expand Up @@ -572,6 +582,8 @@ public EntryEditor getEntryEditor() {
* @param entry The entry to edit.
*/
public void showAndEdit(BibEntry entry) {
incrementViewCount(entry);

if (!splitPane.getItems().contains(entryEditor)) {
splitPane.getItems().addLast(entryEditor);
mode = PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR;
Expand Down Expand Up @@ -1197,4 +1209,26 @@ public String toString() {
public LibraryTabContainer getLibraryTabContainer() {
return tabContainer;
}

/**
* Takes an entry and increments the number of times it has been viewed by 1.
*/
Comment on lines +1209 to +1211
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does more - there is a kind of if

private void incrementViewCount(BibEntry entry) {
MVStore mvStore = PopularityGroup.getMVStore();
synchronized (mvStore) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the maps are opened after load, there is no need for synchronized (see below)

MVMap<String, Integer> viewCounts = mvStore.openMap("entryViewCounts");
MVMap<String, Long> lastViewTimestamps = mvStore.openMap("lastViewTimestamps");
Comment on lines +1215 to +1216
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nono - the map is a class variable - no need to open the map each time for writing.

You need to shut down the mvstore when the tab is closed.

The the lifecycle of the AiService or search for examples.


long currentTime = System.currentTimeMillis();
String uniqueKey = PopularityGroup.getUniqueKeyForEntry(entry);
long lastViewTime = lastViewTimestamps.getOrDefault(uniqueKey, 0L);

if (currentTime - lastViewTime >= 3600000 && EntryTabViewModel.trackViewsProperty().get()) { // 3600000 milliseconds in one hour
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduce constant

Copy link
Member

@HoussemNasri HoussemNasri Nov 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use java.time.Duration.

int currentCount = viewCounts.getOrDefault(uniqueKey, 0);
viewCounts.put(uniqueKey, currentCount + 1);
lastViewTimestamps.put(uniqueKey, currentTime);
mvStore.commit(); // Save changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI comment. Delete.

}
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.jabref.model.entry.identifier.Identifier;

import com.airhacks.afterburner.injection.Injector;
import net.harawata.appdirs.AppDirsFactory;
import org.slf4j.LoggerFactory;

import static org.jabref.model.entry.field.StandardField.PDF;
Expand Down Expand Up @@ -386,4 +387,19 @@ public void moveToTrash(Path path) {
public boolean moveToTrashSupported() {
return Desktop.getDesktop().isSupported(Desktop.Action.MOVE_TO_TRASH);
}

public static Path getOtherDataDir() {
Path userDataDir = Path.of(AppDirsFactory.getInstance().getUserDataDir(
OS.APP_DIR_APP_NAME,
"views",
OS.APP_DIR_APP_AUTHOR));

try {
Files.createDirectories(userDataDir);
} catch (IOException e) {
// Update this for more elegant error handling
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI generated. Pleasemwork on this

}

return userDataDir;
}
}
13 changes: 13 additions & 0 deletions src/main/java/org/jabref/gui/groups/GroupDialog.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ToggleGroup fx:id="hierarchicalContext"/>
<ToggleGroup fx:id="type"/>
<ToggleGroup fx:id="autoGroupOptions"/>
<ToggleGroup fx:id="popularityOptions"/>
</fx:define>
<VBox spacing="10.0" minWidth="200">
<padding>
Expand Down Expand Up @@ -99,6 +100,12 @@
<Tooltip text="%Group containing entries cited in a given TeX file"/>
</tooltip>
</RadioButton>
<RadioButton fx:id="popularityButton" toggleGroup="$type" wrapText="true"
text="%Popularity">
<tooltip>
<Tooltip text="%Group containing entries cited in a given TeX file"/>
</tooltip>
</RadioButton>
Comment on lines +103 to +108
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another functionality. CHANGELOG only talks about view count. Did you work on another issue, too?

</VBox>
<Separator orientation="VERTICAL"/>
<StackPane HBox.hgrow="ALWAYS">
Expand Down Expand Up @@ -183,6 +190,12 @@
<Button onAction="#texGroupBrowse" text="%Browse" prefHeight="30.0"/>
</HBox>
</VBox>
<VBox visible="${popularityButton.selected}" spacing="10.0">
<Label text="%Time Period"/>
<ComboBox fx:id="timePeriodCombo"/>
<Label text="%Maximum Number of Entries"/>
<ComboBox fx:id="maxEntriesCombo"/>
</VBox>
</StackPane>
</HBox>
</VBox>
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/jabref/gui/groups/GroupDialogView.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public class GroupDialogView extends BaseDialog<AbstractGroup> {
@FXML private RadioButton searchRadioButton;
@FXML private RadioButton autoRadioButton;
@FXML private RadioButton texRadioButton;
@FXML private RadioButton popularityButton;

// Option Groups
@FXML private TextField keywordGroupSearchTerm;
Expand All @@ -101,6 +102,9 @@ public class GroupDialogView extends BaseDialog<AbstractGroup> {

@FXML private TextField texGroupFilePath;

@FXML private ComboBox<String> timePeriodCombo;
@FXML private ComboBox<Integer> maxEntriesCombo;

private final EnumMap<GroupHierarchyType, String> hierarchyText = new EnumMap<>(GroupHierarchyType.class);
private final EnumMap<GroupHierarchyType, String> hierarchyToolTip = new EnumMap<>(GroupHierarchyType.class);

Expand Down Expand Up @@ -201,6 +205,15 @@ public void initialize() {
searchRadioButton.selectedProperty().bindBidirectional(viewModel.typeSearchProperty());
autoRadioButton.selectedProperty().bindBidirectional(viewModel.typeAutoProperty());
texRadioButton.selectedProperty().bindBidirectional(viewModel.typeTexProperty());
popularityButton.selectedProperty().bindBidirectional(viewModel.typePopularityProperty());
popularityButton.disableProperty().bind(viewModel.trackViewsEnabledProperty().not());

timePeriodCombo.visibleProperty().bind(popularityButton.selectedProperty());
maxEntriesCombo.visibleProperty().bind(popularityButton.selectedProperty());
timePeriodCombo.valueProperty().bindBidirectional(viewModel.timePeriodProperty());
maxEntriesCombo.valueProperty().bindBidirectional(viewModel.maxEntriesProperty());
timePeriodCombo.setItems(FXCollections.observableArrayList("Last week", "Last month", "Last year", "All time"));
maxEntriesCombo.setItems(FXCollections.observableArrayList(10, 20, 50, 100, 999));
Comment on lines +211 to +216
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please group by button, not by method. Otherwise, it is hard to read.


keywordGroupSearchTerm.textProperty().bindBidirectional(viewModel.keywordGroupSearchTermProperty());
keywordGroupSearchField.textProperty().bindBidirectional(viewModel.keywordGroupSearchFieldProperty());
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
Expand All @@ -28,6 +29,7 @@
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.importer.actions.SearchGroupsMigrationAction;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.preferences.entry.EntryTabViewModel;
import org.jabref.gui.util.FileDialogConfiguration;
import org.jabref.logic.auxparser.DefaultAuxParser;
import org.jabref.logic.groups.DefaultGroupsFactory;
Expand All @@ -46,6 +48,7 @@
import org.jabref.model.groups.ExplicitGroup;
import org.jabref.model.groups.GroupHierarchyType;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.groups.PopularityGroup;
import org.jabref.model.groups.RegexKeywordGroup;
import org.jabref.model.groups.SearchGroup;
import org.jabref.model.groups.TexGroup;
Expand Down Expand Up @@ -79,6 +82,7 @@ public class GroupDialogViewModel {
private final BooleanProperty typeSearchProperty = new SimpleBooleanProperty();
private final BooleanProperty typeAutoProperty = new SimpleBooleanProperty();
private final BooleanProperty typeTexProperty = new SimpleBooleanProperty();
private final BooleanProperty typePopularityProperty = new SimpleBooleanProperty();

// Option Groups
private final StringProperty keywordGroupSearchTermProperty = new SimpleStringProperty("");
Expand All @@ -98,6 +102,9 @@ public class GroupDialogViewModel {

private final StringProperty texGroupFilePathProperty = new SimpleStringProperty("");

private final Property<Integer> maxEntriesProperty = new SimpleObjectProperty<>();
private final StringProperty timePeriodProperty = new SimpleStringProperty();

private Validator nameValidator;
private Validator nameContainsDelimiterValidator;
private Validator sameNameValidator;
Expand Down Expand Up @@ -373,6 +380,13 @@ public AbstractGroup resultConverter(ButtonType button) {
new DefaultAuxParser(new BibDatabase()),
fileUpdateMonitor,
currentDatabase.getMetaData());
} else if (typePopularityProperty.getValue()) {
resultingGroup = new PopularityGroup(
groupName,
groupHierarchySelectedProperty.getValue(),
currentDatabase.getEntries(),
maxEntriesProperty,
timePeriodProperty);
}

if (resultingGroup != null) {
Expand Down Expand Up @@ -585,6 +599,10 @@ public BooleanProperty typeTexProperty() {
return typeTexProperty;
}

public BooleanProperty typePopularityProperty() {
return typePopularityProperty;
}

public StringProperty keywordGroupSearchTermProperty() {
return keywordGroupSearchTermProperty;
}
Expand Down Expand Up @@ -656,4 +674,16 @@ private boolean groupOrSubgroupIsSearchGroup(GroupTreeNode groupTreeNode) {
}
return false;
}

public BooleanProperty trackViewsEnabledProperty() {
return EntryTabViewModel.trackViewsProperty();
}

public StringProperty timePeriodProperty() {
return timePeriodProperty;
}

public Property<Integer> maxEntriesProperty() {
return maxEntriesProperty;
}
}
11 changes: 11 additions & 0 deletions src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.groups.KeywordGroup;
import org.jabref.model.groups.LastNameGroup;
import org.jabref.model.groups.PopularityGroup;
import org.jabref.model.groups.RegexKeywordGroup;
import org.jabref.model.groups.SearchGroup;
import org.jabref.model.groups.TexGroup;
Expand Down Expand Up @@ -428,6 +429,8 @@ public boolean canAddEntriesIn() {
return false;
} else if (group instanceof TexGroup) {
return false;
} else if (group instanceof PopularityGroup) {
return false;
} else {
throw new UnsupportedOperationException("canAddEntriesIn method not yet implemented in group: " + group.getClass().getName());
}
Expand All @@ -453,6 +456,8 @@ public boolean canBeDragged() {
return true;
} else if (group instanceof TexGroup) {
return true;
} else if (group instanceof PopularityGroup) {
return true;
} else {
throw new UnsupportedOperationException("canBeDragged method not yet implemented in group: " + group.getClass().getName());
}
Expand All @@ -478,6 +483,8 @@ public boolean canAddGroupsIn() {
return false;
} else if (group instanceof TexGroup) {
return true;
} else if (group instanceof PopularityGroup) {
return false;
} else {
throw new UnsupportedOperationException("canAddGroupsIn method not yet implemented in group: " + group.getClass().getName());
}
Expand All @@ -503,6 +510,8 @@ public boolean canRemove() {
return true;
} else if (group instanceof TexGroup) {
return true;
} else if (group instanceof PopularityGroup) {
return true;
} else {
throw new UnsupportedOperationException("canRemove method not yet implemented in group: " + group.getClass().getName());
}
Expand All @@ -528,6 +537,8 @@ public boolean isEditable() {
return true;
} else if (group instanceof TexGroup) {
return true;
} else if (group instanceof PopularityGroup) {
return true;
} else {
throw new UnsupportedOperationException("isEditable method not yet implemented in group: " + group.getClass().getName());
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/jabref/gui/preferences/entry/EntryTab.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,15 @@
<Label styleClass="sectionHeader" text="%Time stamp"/>
<CheckBox fx:id="addCreationDate" text="%Add timestamp to new entries (field &quot;creationdate&quot;)"/>
<CheckBox fx:id="addModificationDate" text="%Add timestamp to modified entries (field &quot;modificationdate&quot;)"/>



<CheckBox fx:id="enableViewTracking" text="%Enable user behaviour analysis"/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enable user behaviour analysis --> wrong word - please try to use the same words "view tracking" below "popularity" is OK, but "bheavior analysis" is even more general. It should be consistent with src/main/java/org/jabref/gui/groups/GroupDialog.fxml.

<CheckBox fx:id="trackViews" text="%Track entry views"
disable="${!enableViewTracking.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>

</fx:root>
16 changes: 15 additions & 1 deletion src/main/java/org/jabref/gui/preferences/entry/EntryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@

public class EntryTab extends AbstractPreferenceTabView<EntryTabViewModel> implements PreferencesTab {


@FXML public CheckBox trackViews;
@FXML private CheckBox enableViewTracking;

@FXML private TextField keywordSeparator;

Expand Down Expand Up @@ -70,6 +71,15 @@ public void initialize() {

addCreationDate.selectedProperty().bindBidirectional(viewModel.addCreationDateProperty());
addModificationDate.selectedProperty().bindBidirectional(viewModel.addModificationDateProperty());
trackViews.selectedProperty().bindBidirectional(EntryTabViewModel.trackViewsProperty());
enableViewTracking.selectedProperty().bindBidirectional(EntryTabViewModel.analysisProperty());

trackViews.selectedProperty().addListener((observable, oldValue, newValue) -> {
viewModel.setTrackViewsEnabled(newValue);
});
enableViewTracking.selectedProperty().addListener((observable, oldValue, newValue) -> {
viewModel.setAnalysisEnabled(newValue);
});

ActionFactory actionFactory = new ActionFactory();
actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.OWNER, dialogService, preferences.getExternalApplicationsPreferences()), markOwnerHelp);
Expand All @@ -79,4 +89,8 @@ public void initialize() {
public String getTabName() {
return Localization.lang("Entry");
}

public CheckBox getTrackViews() {
return trackViews;
}
}
Loading
Loading