Skip to content
New issue

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

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

Already on GitHub? # to your account

Add fix options for integrity issues #12495

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9b11edf
Add fix options for integrity issues
priyanshu16095 Feb 12, 2025
02d8e75
Fix the check
priyanshu16095 Feb 12, 2025
754b6ad
Add a missing entry for Localization.lang
priyanshu16095 Feb 12, 2025
696f904
Fix the check
priyanshu16095 Feb 12, 2025
4ab36a8
Refactor code
priyanshu16095 Feb 14, 2025
28de796
Fix the check
priyanshu16095 Feb 14, 2025
70ae8eb
Add a CHANGELOG.md entry
priyanshu16095 Feb 14, 2025
77df6ea
Refactor the code
priyanshu16095 Feb 19, 2025
30c81ce
Make use of IntegrityIssue enum
priyanshu16095 Feb 20, 2025
5b389da
Add keys in Localization.lang
priyanshu16095 Feb 20, 2025
3a0d9a1
Refactor the code
priyanshu16095 Feb 20, 2025
a04e06a
Remove use of enum
priyanshu16095 Feb 20, 2025
8e9341a
Remove use of enum from PageChecker class
priyanshu16095 Feb 20, 2025
235c74f
Add fixAll method
priyanshu16095 Feb 20, 2025
9839137
Add a check for fixAll and fixByType method
priyanshu16095 Feb 20, 2025
5754680
Fix a bug
priyanshu16095 Feb 20, 2025
8f7bdc2
Use dialogService.notify() to notify
priyanshu16095 Feb 22, 2025
f57719c
Remove unused imports
priyanshu16095 Feb 22, 2025
2105a13
Merge branch 'main' into integrityCheckActions
priyanshu16095 Feb 22, 2025
e37ba79
Merge branch 'main' into integrityCheckActions
priyanshu16095 Feb 23, 2025
139777e
Add tests for methods
priyanshu16095 Feb 25, 2025
39dbfd1
Fix the check and updates based on review
priyanshu16095 Feb 26, 2025
16e9817
Use enum for passing issue message
priyanshu16095 Feb 28, 2025
102e789
Fix the checkstyle check
priyanshu16095 Feb 28, 2025
2d1a5a4
Use enum in remaining checkers
priyanshu16095 Feb 28, 2025
361765b
Fix the check
priyanshu16095 Feb 28, 2025
19a7881
Revert "Fix the checkstyle check"
priyanshu16095 Feb 28, 2025
6a5da7a
Fix the check
priyanshu16095 Feb 28, 2025
1bfd407
Fix the check
priyanshu16095 Feb 28, 2025
fd0045f
Fix the check
priyanshu16095 Feb 28, 2025
9dfed1a
Fix the check
priyanshu16095 Feb 28, 2025
7a45ddd
Add JavaDoc comments
priyanshu16095 Feb 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- We added a feature to fix integrity issues one at a time or similar issues by selecting a type, or to fix all at once. [#11419](https://github.com/JabRef/jabref/issues/11419)
- We added a new CLI that supports txt, csv, and console-based output for consistency in BibTeX entries. [#11984](https://github.com/JabRef/jabref/issues/11984)
- We added a new dialog for bibliography consistency check. [#11950](https://github.com/JabRef/jabref/issues/11950)
- We added a feature for copying entries to libraries, available via the context menu, with an option to include cross-references. [#12374](https://github.com/JabRef/jabref/pull/12374)
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/frame/MainMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ private void createMenu() {
quality.getItems().addAll(
factory.createMenuItem(StandardActions.FIND_DUPLICATES, new DuplicateSearch(frame::getCurrentLibraryTab, dialogService, stateManager, preferences, entryTypesManager, taskExecutor)),
factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(dialogService, stateManager, undoManager, preferences)),
factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, (UiTaskExecutor) taskExecutor, abbreviationRepository)),
factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, (UiTaskExecutor) taskExecutor, preferences, undoManager, abbreviationRepository)),
factory.createMenuItem(StandardActions.CHECK_CONSISTENCY, new ConsistencyCheckAction(frame::getCurrentLibraryTab, dialogService, stateManager, preferences, entryTypesManager, (UiTaskExecutor) taskExecutor)),
factory.createMenuItem(StandardActions.CLEANUP_ENTRIES, new CleanupAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, taskExecutor, undoManager)),

Expand Down
11 changes: 10 additions & 1 deletion src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.List;
import java.util.function.Supplier;

import javax.swing.undo.UndoManager;

import javafx.collections.ObservableList;
import javafx.concurrent.Task;

Expand All @@ -17,6 +19,7 @@
import org.jabref.logic.integrity.IntegrityMessage;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

Expand All @@ -29,19 +32,25 @@ public class IntegrityCheckAction extends SimpleCommand {
private final Supplier<LibraryTab> tabSupplier;
private final GuiPreferences preferences;
private final StateManager stateManager;
private final CliPreferences cliPreferences;
private final UndoManager undoManager;
private final JournalAbbreviationRepository abbreviationRepository;

public IntegrityCheckAction(Supplier<LibraryTab> tabSupplier,
GuiPreferences preferences,
DialogService dialogService,
StateManager stateManager,
UiTaskExecutor taskExecutor,
CliPreferences cliPreferences,
UndoManager undoManager,
JournalAbbreviationRepository abbreviationRepository) {
this.tabSupplier = tabSupplier;
this.stateManager = stateManager;
this.taskExecutor = taskExecutor;
this.preferences = preferences;
this.dialogService = dialogService;
this.cliPreferences = cliPreferences;
this.undoManager = undoManager;
this.abbreviationRepository = abbreviationRepository;
this.executable.bind(needsDatabase(this.stateManager));
}
Expand Down Expand Up @@ -78,7 +87,7 @@ protected List<IntegrityMessage> call() {
if (messages.isEmpty()) {
dialogService.notify(Localization.lang("No problems found."));
} else {
dialogService.showCustomDialogAndWait(new IntegrityCheckDialog(messages, tabSupplier.get()));
dialogService.showCustomDialogAndWait(new IntegrityCheckDialog(messages, tabSupplier, dialogService, stateManager, taskExecutor, cliPreferences, undoManager));
}
});
task.setOnFailed(event -> dialogService.showErrorDialogAndWait("Integrity check failed.", task.getException()));
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.control.ComboBox?>
<DialogPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="600.0" prefWidth="700.0"
xmlns="http://javafx.com/javafx/8.0.121" fx:controller="org.jabref.gui.integrity.IntegrityCheckDialog">
<content>
<VBox spacing="4.0">
<TableView fx:id="messagesTable" prefHeight="550" prefWidth="700.0" VBox.vgrow="ALWAYS" HBox.hgrow="ALWAYS">
<VBox spacing="10.0">
<TableView fx:id="messagesTable" prefHeight="550" prefWidth="750.0" VBox.vgrow="ALWAYS" HBox.hgrow="ALWAYS">
<columns>
<TableColumn fx:id="keyColumn" prefWidth="150.0" maxWidth="200" text="%Citation key"/>
<TableColumn fx:id="fieldColumn" prefWidth="110.0" maxWidth="200" text="%Field"/>
<TableColumn fx:id="messageColumn" prefWidth="400" text="%Message"/>
<TableColumn fx:id="fieldColumn" prefWidth="100.0" maxWidth="200" text="%Field"/>
<TableColumn fx:id="messageColumn" prefWidth="300" text="%Message"/>
<TableColumn fx:id="fixesColumn" prefWidth="200.0" maxWidth="200" text="%Fixes"/>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN"/>
Expand All @@ -26,7 +29,12 @@
<MenuButton fx:id="keyFilterButton" prefHeight="30.0" maxHeight="30.0" text="%Citation key filters"/>
<MenuButton fx:id="fieldFilterButton" prefHeight="30.0" maxHeight="30.0" text="%Field filters"/>
<MenuButton fx:id="messageFilterButton" prefHeight="30.0" maxHeight="30.0" text="%Message filters"/>
<Button onAction="#clearFilters" text="%Clear filters"/>
</HBox>
<HBox maxHeight="30" spacing="4.0" alignment="CENTER">
<Button prefHeight="20.0" text="%Fix all"/>
<Region HBox.hgrow="ALWAYS" />
<ComboBox fx:id="entryTypeCombo" maxWidth="400" />
<Button onAction="#fixByType" alignment="CENTER_RIGHT" prefHeight="20.0" text="%Fix by type"/>
</HBox>
</VBox>
</content>
Expand Down
118 changes: 113 additions & 5 deletions src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
package org.jabref.gui.integrity;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.swing.undo.UndoManager;

import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.input.MouseButton;
import javafx.stage.Modality;

import org.jabref.gui.DialogService;
import org.jabref.gui.LibraryTab;
import org.jabref.gui.StateManager;
import org.jabref.gui.theme.ThemeManager;
import org.jabref.gui.util.BaseDialog;
import org.jabref.gui.util.ValueTableCellFactory;
import org.jabref.logic.integrity.IntegrityMessage;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.entry.field.Field;

import com.airhacks.afterburner.views.ViewLoader;
import jakarta.inject.Inject;
Expand All @@ -30,20 +46,40 @@ public class IntegrityCheckDialog extends BaseDialog<Void> {
@FXML private TableColumn<IntegrityMessage, String> keyColumn;
@FXML private TableColumn<IntegrityMessage, String> fieldColumn;
@FXML private TableColumn<IntegrityMessage, String> messageColumn;
@FXML private TableColumn<IntegrityMessage, String> fixesColumn;
@FXML private MenuButton keyFilterButton;
@FXML private MenuButton fieldFilterButton;
@FXML private MenuButton messageFilterButton;
@FXML private ComboBox<String> entryTypeCombo;

@Inject private ThemeManager themeManager;

private final List<IntegrityMessage> messages;
private final LibraryTab libraryTab;
private final Supplier<LibraryTab> tabSupplier;
private final DialogService dialogService;
private final StateManager stateManager;
private final TaskExecutor taskExecutor;
private final CliPreferences preferences;
private final UndoManager undoManager;

private IntegrityCheckDialogViewModel viewModel;
private TableFilter<IntegrityMessage> tableFilter;

public IntegrityCheckDialog(List<IntegrityMessage> messages, LibraryTab libraryTab) {
public IntegrityCheckDialog(List<IntegrityMessage> messages,
Supplier<LibraryTab> tabSupplier,
DialogService dialogService,
StateManager stateManager,
TaskExecutor taskExecutor,
CliPreferences preferences,
UndoManager undoManager) {
this.messages = messages;
this.libraryTab = libraryTab;
this.tabSupplier = tabSupplier;
this.dialogService = dialogService;
this.stateManager = stateManager;
this.taskExecutor = taskExecutor;
this.preferences = preferences;
this.undoManager = undoManager;

this.setTitle(Localization.lang("Check integrity"));
this.initModality(Modality.NONE);

Expand All @@ -57,7 +93,7 @@ public IntegrityCheckDialog(List<IntegrityMessage> messages, LibraryTab libraryT
private void onSelectionChanged(ListChangeListener.Change<? extends IntegrityMessage> change) {
if (change.next()) {
change.getAddedSubList().stream().findFirst().ifPresent(message ->
libraryTab.editEntryAndFocusField(message.entry(), message.field()));
tabSupplier.get().editEntryAndFocusField(message.entry(), message.field()));
}
}

Expand All @@ -67,14 +103,49 @@ public IntegrityCheckDialogViewModel getViewModel() {

@FXML
private void initialize() {
viewModel = new IntegrityCheckDialogViewModel(messages);
viewModel = new IntegrityCheckDialogViewModel(messages, tabSupplier, dialogService, stateManager, taskExecutor, preferences, undoManager);

messagesTable.getSelectionModel().getSelectedItems().addListener(this::onSelectionChanged);
messagesTable.setItems(viewModel.getMessages());
updateEntryTypeCombo();
keyColumn.setCellValueFactory(row -> new ReadOnlyStringWrapper(row.getValue().entry().getCitationKey().orElse("")));
fieldColumn.setCellValueFactory(row -> new ReadOnlyStringWrapper(row.getValue().field().getDisplayName()));
messageColumn.setCellValueFactory(row -> new ReadOnlyStringWrapper(row.getValue().message()));

fixesColumn.setCellFactory(row -> new TableCell<>() {
private final Button button = new Button();

@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
setGraphic(null);
return;
}
IntegrityMessage rowData = getTableRow().getItem();
configureAction(rowData);
}

private void configureAction(IntegrityMessage message) {
Optional<IntegrityIssue> issue = IntegrityIssue.fromField(message.field());
if (issue.isPresent() && issue.get().getFix() != null) {
configureButton(issue.get().getFix(), () -> {
viewModel.fix(issue.get(), message);
removeRowFromTable(message);
});
setGraphic(button);
return;
}
setGraphic(null);
}

private void configureButton(String text, Runnable action) {
button.setText(text);
button.setPrefHeight(20.0);
button.setOnAction(event -> action.run());
}
});

new ValueTableCellFactory<IntegrityMessage, String>()
.withText(Function.identity())
.withTooltip(Function.identity())
Expand Down Expand Up @@ -116,4 +187,41 @@ public void clearFilters() {
});
}
}

private void removeRowFromTable(IntegrityMessage message) {
ObservableList<IntegrityMessage> mutableMessages = FXCollections.observableArrayList(messagesTable.getItems());
mutableMessages.remove(message);
messagesTable.setItems(mutableMessages);
}

private void updateEntryTypeCombo() {
Set<Field> entryTypes = viewModel.getEntryTypes();
entryTypeCombo.getItems().clear();

Arrays.stream(IntegrityIssue.values())
.filter(issue -> entryTypes.contains(issue.getField()))
.forEach(issue -> entryTypeCombo.getItems().add(issue.getText()));

if (!entryTypeCombo.getItems().isEmpty()) {
entryTypeCombo.getSelectionModel().selectFirst();
}
}

@FXML
private void fixByType() {
String selectedType = entryTypeCombo.getSelectionModel().getSelectedItem();
Optional<IntegrityIssue> selectedIssue = Arrays.stream(IntegrityIssue.values())
.filter(issue -> issue.getText().equals(selectedType))
.findFirst();
if (selectedIssue.isPresent()) {
for (IntegrityMessage message : messages) {
if (message.field().equals(selectedIssue.get().getField())) {
viewModel.fix(selectedIssue.get(), message);
removeRowFromTable(message);
viewModel.removeFromEntryTypes(message.field().getDisplayName());
}
}
}
updateEntryTypeCombo();
}
}
Loading
Loading