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

Add fix options for integrity issues #12495

Draft
wants to merge 62 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 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
71efd96
Merge branch 'main' into integrityCheckActions
koppor Mar 11, 2025
409797f
Add integrityIssues.bib file and renamed varibles to singular form
priyanshu16095 Mar 12, 2025
cb0c1be
Merge branch 'main' into integrityCheckActions
priyanshu16095 Mar 13, 2025
3e94343
Fix the working of button
priyanshu16095 Mar 12, 2025
30d4bbb
Sort the issues in alphabetical order
priyanshu16095 Mar 12, 2025
2990e3d
Sort the issues alphabetically and show 'No fix availalbe' in the com…
priyanshu16095 Mar 13, 2025
647cac8
Add method fix
priyanshu16095 Mar 13, 2025
225d235
Remove use of magic number
priyanshu16095 Mar 13, 2025
0cb580f
Some changes in integrityissues.bib
priyanshu16095 Mar 13, 2025
4ea6831
Make use of fail-fast principle
priyanshu16095 Mar 13, 2025
2302dec
Remove exclaimation mark
priyanshu16095 Mar 13, 2025
71a6510
Fix the issue
priyanshu16095 Mar 13, 2025
a608a22
Fix the checks
priyanshu16095 Mar 13, 2025
e52907f
Add readme.md for integrityIssues.bib file
priyanshu16095 Mar 13, 2025
eff137a
Change the string to 'BibTex field only'
priyanshu16095 Mar 13, 2025
621ebaf
Sovle the parameters issue
priyanshu16095 Mar 13, 2025
bcb079f
Correct the spelling of BibTeX
priyanshu16095 Mar 13, 2025
4861b01
Add one more issue in IntegrityIssue
priyanshu16095 Mar 13, 2025
e60595c
Fix the rewrite check
priyanshu16095 Mar 13, 2025
3cf54dd
Merge branch 'main' into integrityCheckActions
koppor Mar 17, 2025
a9dbf98
Revert "Correct the spelling of BibTeX"
priyanshu16095 Mar 17, 2025
cafaf0c
Merge remote-tracking branch 'origin/integrityCheckActions' into inte…
priyanshu16095 Mar 17, 2025
e11499a
Remove csl modules
priyanshu16095 Mar 17, 2025
157dfe8
Remove csl locales
priyanshu16095 Mar 17, 2025
6f86c65
Correct the spelling of BibTeX
priyanshu16095 Mar 17, 2025
8502e76
Try to remove diagService parameter
koppor Mar 19, 2025
a717a45
Fix indent
koppor Mar 19, 2025
ee71b7f
Remove fixes and tests
priyanshu16095 Mar 20, 2025
b979659
Merge branch 'main' into integrityCheckActions
priyanshu16095 Mar 20, 2025
0656364
Remove integrityIssues.bib file
priyanshu16095 Mar 21, 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 Welcome tab which shows a welcome screen if no database is open. [#12272](https://github.com/JabRef/jabref/issues/12272)
- We added <kbd>F5</kbd> as a shortcut key for fetching data and <kbd>Alt+F</kbd> as a shortcut for looking up data using DOI. [#11802](https://github.com/JabRef/jabref/issues/11802)
- We added a feature to rename the subgroup, with the keybinding (<kbd>F2</kbd>) for quick access. [#11896](https://github.com/JabRef/jabref/issues/11896)
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/jabref/gui/JabRefGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.jabref.logic.util.BuildInfo;
import org.jabref.logic.util.FallbackExceptionHandler;
import org.jabref.logic.util.HeadlessExecutorService;
import org.jabref.logic.util.NotificationService;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.logic.util.WebViewStore;
import org.jabref.model.entry.BibEntryTypesManager;
Expand Down Expand Up @@ -162,6 +163,7 @@ public void initialize() {

JabRefGUI.dialogService = new JabRefDialogService(mainStage);
Injector.setModelOrService(DialogService.class, dialogService);
Injector.setModelOrService(NotificationService.class, dialogService);

JabRefGUI.clipBoardManager = new ClipBoardManager();
Injector.setModelOrService(ClipBoardManager.class, clipBoardManager);
Expand Down
24 changes: 16 additions & 8 deletions src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,33 @@
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?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="600" 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="150.0" maxWidth="200" text="%Field"/>
<TableColumn fx:id="messageColumn" prefWidth="300" text="%Message"/>
<TableColumn fx:id="fixesColumn" prefWidth="200.0" maxWidth="200" text="%Fix"/>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN"/>
</columnResizePolicy>
</TableView>
<HBox maxHeight="30" spacing="4.0">
<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"/>
<MenuButton fx:id="keyFilterButton" prefHeight="30.0" maxHeight="30.0" text="%Citation key filter"/>
<MenuButton fx:id="fieldFilterButton" prefHeight="30.0" maxHeight="30.0" text="%Field filter"/>
<MenuButton fx:id="messageFilterButton" prefHeight="30.0" maxHeight="30.0" text="%Message filter"/>
</HBox>
<HBox maxHeight="30" spacing="10.0" alignment="CENTER_RIGHT">
<HBox spacing="4.0">
<ComboBox fx:id="entryTypeCombo" maxWidth="400" />
<Button onAction="#fixByType" prefHeight="20.0" text="%Fix by type"/>
</HBox>
<Button onAction="#fixAll" prefHeight="20.0" text="%Fix all"/>
</HBox>
</VBox>
</content>
Expand Down
151 changes: 150 additions & 1 deletion src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package org.jabref.gui.integrity;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
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;
Expand All @@ -18,8 +29,10 @@
import org.jabref.gui.util.BaseDialog;
import org.jabref.gui.util.ValueTableCellFactory;
import org.jabref.gui.util.ViewModelTableRowFactory;
import org.jabref.logic.integrity.IntegrityIssue;
import org.jabref.logic.integrity.IntegrityMessage;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.NotificationService;

import com.airhacks.afterburner.views.ViewLoader;
import jakarta.inject.Inject;
Expand All @@ -31,20 +44,28 @@ 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;
@Inject private NotificationService notificationService;

private final List<IntegrityMessage> messages;
private final LibraryTab libraryTab;

private IntegrityCheckDialogViewModel viewModel;
private TableFilter<IntegrityMessage> tableFilter;

public IntegrityCheckDialog(List<IntegrityMessage> messages, LibraryTab libraryTab) {
private final double FIX_BUTTON_HEIGHT = 20.0;

public IntegrityCheckDialog(List<IntegrityMessage> messages,
LibraryTab libraryTab) {
this.messages = messages;
this.libraryTab = libraryTab;

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

Expand Down Expand Up @@ -76,10 +97,55 @@ private void initialize() {
.withOnMouseClickedEvent(this::handleRowClick)
.install(messagesTable);
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);
}

/**
* Configures the action for the button based on the provided {@link IntegrityMessage}.
* If a fix is available for the message, the button is configured to apply the fix when clicked.
*
* @param message the {@link IntegrityMessage} to check for available fixes
*/
private void configureAction(IntegrityMessage message) {
Optional<IntegrityIssue> issue = IntegrityIssue.fromMessage(message);
if (issue.isEmpty()) {
return;
}
if (issue.get().getFix().isEmpty()) {
setGraphic(new Label(Localization.lang("No fix available")));
return;
}
configureButton(issue.get().getFix().toString(), () -> {
viewModel.fix(issue.get(), message);
viewModel.removeFromEntryTypes(message.field().getDisplayName());
Platform.runLater(() -> viewModel.columnsListProperty().getValue().removeIf(column -> Objects.equals(column.message(), message.message())));
});
setGraphic(button);
}

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

new ValueTableCellFactory<IntegrityMessage, String>()
.withText(Function.identity())
.withTooltip(Function.identity())
Expand All @@ -91,6 +157,8 @@ private void initialize() {
addMessageColumnFilter(keyColumn, keyFilterButton);
addMessageColumnFilter(fieldColumn, fieldFilterButton);
addMessageColumnFilter(messageColumn, messageFilterButton);

messagesTable.itemsProperty().bind(viewModel.columnsListProperty());
}

private void addMessageColumnFilter(TableColumn<IntegrityMessage, String> messageColumn, MenuButton messageFilterButton) {
Expand Down Expand Up @@ -121,4 +189,85 @@ public void clearFilters() {
});
}
}

private void updateEntryTypeCombo() {
Set<String> entryTypes = viewModel.getEntryTypes();
Set<String> uniqueTexts = new HashSet<>();
entryTypeCombo.getItems().clear();

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

if (entryTypeCombo.getItems().isEmpty()) {
entryTypeCombo.getItems().add(Localization.lang("No fix available"));
}
entryTypeCombo.getSelectionModel().selectFirst();
}

public void fix(IntegrityIssue issue, IntegrityMessage message) {
viewModel.fix(issue, message);
viewModel.removeFromEntryTypes(message.field().getDisplayName());
Platform.runLater(() -> viewModel.columnsListProperty().getValue().removeIf(column -> Objects.equals(column.message(), message.message())));
}

/**
* Checks if a given {@link IntegrityMessage} has a fix available.
*
* @param message the {@link IntegrityMessage} to check
* @return {@code true} if a fix is available, {@code false} otherwise
*/
private boolean hasFix(IntegrityMessage message) {
return message != null && message.field() != null && IntegrityIssue.fromMessage(message)
.map(issue -> issue.getFix().isPresent())
.orElse(false);
}

/**
* Attempts to fix all {@link IntegrityMessage} objects of the selected type.
* If fixes are available, they are applied, and the fixed messages are removed.
* A notification is shown to indicate success or failure.
*/
@FXML
private void fixByType() {
AtomicBoolean fixed = new AtomicBoolean(false);

String selectedType = entryTypeCombo.getSelectionModel().getSelectedItem();
Optional<IntegrityIssue> selectedIssue = Arrays.stream(IntegrityIssue.values())
.filter(issue -> issue.getText().equals(selectedType))
.findFirst();

selectedIssue.ifPresent(issue -> {
messagesTable.getItems().stream()
// Filter messages matching the selected issue type and have a fix
.filter(message -> message.message().equals(issue.getText()) && hasFix(message))
.forEach(message -> {
fix(issue, message);
fixed.set(true);
});
});

updateEntryTypeCombo();

if (fixed.get()) {
notificationService.notify(Localization.lang("Fixed successfully."));
} else {
notificationService.notify(Localization.lang("No fixes available."));
}
}

/**
* Attempts to fix all {@link IntegrityMessage} objects that have a fix available.
* Messages with applicable fixes are processed, and the corresponding UI elements are updated.
*/
@FXML
private void fixAll() {
messagesTable.getItems().stream()
.filter(this::hasFix) // Filter all messages that have a fix
.forEach(message -> IntegrityIssue.fromMessage(message).ifPresent(issue -> fix(issue, message)));

updateEntryTypeCombo();
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,53 @@
package org.jabref.gui.integrity;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;

import org.jabref.gui.AbstractViewModel;
import org.jabref.logic.integrity.IntegrityIssue;
import org.jabref.logic.integrity.IntegrityMessage;

public class IntegrityCheckDialogViewModel extends AbstractViewModel {

private final ListProperty<IntegrityMessage> columnsListProperty;
private final ObservableList<IntegrityMessage> messages;
private final ObservableSet<String> entryTypes;

public IntegrityCheckDialogViewModel(List<IntegrityMessage> messages) {
this.messages = FXCollections.observableArrayList(messages);

Set<String> types = messages.stream()
.map(IntegrityMessage::message)
.collect(Collectors.toSet());
this.entryTypes = FXCollections.observableSet(types);

this.columnsListProperty = new SimpleListProperty<>(FXCollections.observableArrayList(messages));
}

public ListProperty<IntegrityMessage> columnsListProperty() {
return this.columnsListProperty;
}

public ObservableList<IntegrityMessage> getMessages() {
return messages;
}

public Set<String> getEntryTypes() {
return entryTypes;
}

public void removeFromEntryTypes(String entry) {
entryTypes.remove(entry);
}

public void fix(IntegrityIssue issue, IntegrityMessage message) {
Copy link

Choose a reason for hiding this comment

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

Method lacks JavaDoc despite being a public method with non-trivial functionality (handling integrity issues). Complex operations should be documented.

// fixes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.List;
import java.util.Map;

import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.Field;

Expand All @@ -21,7 +20,7 @@ public List<IntegrityMessage> check(BibEntry entry) {
for (Map.Entry<Field, String> field : entry.getFieldMap().entrySet()) {
boolean asciiOnly = CharMatcher.ascii().matchesAllOf(field.getValue());
if (!asciiOnly) {
results.add(new IntegrityMessage(Localization.lang("Non-ASCII encoded character found"), entry,
results.add(new IntegrityMessage(IntegrityIssue.NON_ASCII_ENCODED_CHARACTER_FOUND.getText(), entry,
field.getKey()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.util.Set;

import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
Expand All @@ -26,7 +25,7 @@ public List<IntegrityMessage> check(BibEntry entry) {
for (Field field : fields) {
Optional<String> value = entry.getFieldLatexFree(field);
value.filter(abbreviationRepository::isAbbreviatedName)
.ifPresent(val -> messages.add(new IntegrityMessage(Localization.lang("abbreviation detected"), entry, field)));
.ifPresent(val -> messages.add(new IntegrityMessage(IntegrityIssue.ABBREVIATION_DETECTED.getText(), entry, field)));
}
return messages;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.util.regex.Pattern;

import org.jabref.logic.bibtex.FieldWriter;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
Expand Down Expand Up @@ -35,7 +34,7 @@ public List<IntegrityMessage> check(BibEntry entry) {
}
if ((hashCount & 1) == 1) { // Check if odd
// # is FieldWriter.BIBTEX_STRING_START_END_SYMBOL
results.add(new IntegrityMessage(Localization.lang("odd number of unescaped '#'"), entry,
results.add(new IntegrityMessage(IntegrityIssue.ODD_NUMBER_OF_UNESCAPED.getText(), entry,
field.getKey()));
}
}
Expand Down
Loading
Loading