-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Integrate with SearchRxiv #15373
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
base: main
Are you sure you want to change the base?
Integrate with SearchRxiv #15373
Changes from 7 commits
37ab8bc
9919a8a
a27d4ac
9f47f53
0f32f15
9a95c12
ec9f863
45303dd
eea0bb9
673dbd6
2b52e3b
be5620e
21f89d9
dcf4774
af6d2eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,16 +36,21 @@ | |
|
|
||
| import com.airhacks.afterburner.views.ViewLoader; | ||
| import jakarta.inject.Inject; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /// This class controls the user interface of the study definition management dialog. The UI elements and their layout | ||
| /// are defined in the FXML file. | ||
| public class ManageStudyDefinitionView extends BaseDialog<SlrStudyAndDirectory> { | ||
| private static final Logger LOGGER = LoggerFactory.getLogger(ManageStudyDefinitionView.class); | ||
|
|
||
| @FXML private TextField studyTitle; | ||
| @FXML private TextField addAuthor; | ||
| @FXML private TextField addResearchQuestion; | ||
| @FXML private TextField addQuery; | ||
| @FXML private TextField studyDirectory; | ||
| @FXML private Button selectStudyDirectory; | ||
| @FXML private Button shareOnSearchRxivButton; | ||
|
|
||
| @FXML private ButtonType saveSurveyButtonType; | ||
| @FXML private Label helpIcon; | ||
|
|
@@ -177,6 +182,14 @@ private void initialize() { | |
| initQueriesTab(); | ||
| initCatalogsTab(); | ||
| initValidationBindings(); | ||
|
|
||
| shareOnSearchRxivButton.disableProperty().bind( | ||
| Bindings.or( | ||
| Bindings.isEmpty(viewModel.getQueries()), | ||
| Bindings.createBooleanBinding( | ||
| () -> viewModel.getCatalogs().stream().noneMatch(StudyCatalogItem::isEnabled), | ||
| viewModel.getCatalogs()) | ||
| )); | ||
| } | ||
|
|
||
| private void updateDirectoryWarning(Path directory) { | ||
|
|
@@ -295,6 +308,13 @@ private void setupCommonPropertiesForTables(Node addControl, | |
| actionColumn.setResizable(false); | ||
| } | ||
|
|
||
| @FXML | ||
| private void shareOnSearchRxiv() { | ||
| viewModel.shareOnSearchRxiv( | ||
| pathToStudyDataDirectory, | ||
| preferences.getExternalApplicationsPreferences()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The view model should get and store external applications preferences, so the method |
||
| } | ||
|
|
||
| private void setupCellFactories(TableColumn<String, String> contentColumn, | ||
| TableColumn<String, String> actionColumn, | ||
| Consumer<String> removeAction) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,8 +17,12 @@ | |
|
|
||
| import org.jabref.gui.DialogService; | ||
| import org.jabref.gui.WorkspacePreferences; | ||
| import org.jabref.gui.desktop.os.NativeDesktop; | ||
| import org.jabref.gui.frame.ExternalApplicationsPreferences; | ||
| import org.jabref.gui.util.DirectoryDialogConfiguration; | ||
| import org.jabref.logic.crawler.StudyRepository; | ||
| import org.jabref.logic.crawler.StudyYamlParser; | ||
| import org.jabref.logic.exporter.SearchRxivExporter; | ||
| import org.jabref.logic.git.GitHandler; | ||
| import org.jabref.logic.git.preferences.GitPreferences; | ||
| import org.jabref.logic.importer.ImportFormatPreferences; | ||
|
|
@@ -55,8 +59,10 @@ public class ManageStudyDefinitionViewModel { | |
| private final ObservableList<String> authors = FXCollections.observableArrayList(); | ||
| private final ObservableList<String> researchQuestions = FXCollections.observableArrayList(); | ||
| private final ObservableList<String> queries = FXCollections.observableArrayList(); | ||
| private final ObservableList<StudyCatalogItem> databases = FXCollections.observableArrayList(); | ||
|
|
||
| // Observe changes to each item's enabledProperty so bindings re-evaluate when catalogs are toggled | ||
| private final ObservableList<StudyCatalogItem> databases = FXCollections.observableArrayList( | ||
|
InAnYan marked this conversation as resolved.
|
||
| item -> new javafx.beans.Observable[] {item.enabledProperty()} | ||
| ); | ||
| // Hold the complement of databases for the selector | ||
| private final SimpleStringProperty directory = new SimpleStringProperty(); | ||
|
|
||
|
|
@@ -218,12 +224,7 @@ public void addQuery(String query) { | |
| } | ||
|
|
||
| public SlrStudyAndDirectory saveStudy() { | ||
| Study study = new Study( | ||
| authors, | ||
| title.getValueSafe(), | ||
| researchQuestions, | ||
| queries.stream().map(StudyQuery::new).collect(Collectors.toList()), | ||
| databases.stream().map(studyDatabaseItem -> new StudyDatabase(studyDatabaseItem.getName(), studyDatabaseItem.isEnabled())).filter(StudyDatabase::isEnabled).collect(Collectors.toList())); | ||
| Study study = buildStudy(); | ||
| Path studyDirectory; | ||
| final String studyDirectoryAsString = directory.getValueSafe(); | ||
| try { | ||
|
|
@@ -257,6 +258,19 @@ public SlrStudyAndDirectory saveStudy() { | |
| return new SlrStudyAndDirectory(study, studyDirectory); | ||
| } | ||
|
|
||
| /// Builds a {@link Study} from the current UI state without persisting it. | ||
| public Study buildStudy() { | ||
| return new Study( | ||
| authors, | ||
| title.getValueSafe(), | ||
| researchQuestions, | ||
| queries.stream().map(StudyQuery::new).collect(Collectors.toList()), | ||
| databases.stream() | ||
| .map(item -> new StudyDatabase(item.getName(), item.isEnabled())) | ||
| .filter(StudyDatabase::isEnabled) | ||
| .collect(Collectors.toList())); | ||
| } | ||
|
|
||
| public Property<String> titleProperty() { | ||
| return title; | ||
| } | ||
|
|
@@ -316,4 +330,30 @@ public StringProperty queriesValidationMessageProperty() { | |
| public StringProperty catalogsValidationMessageProperty() { | ||
| return catalogsValidationMessage; | ||
| } | ||
|
|
||
| public void shareOnSearchRxiv(Path initialDirectory, ExternalApplicationsPreferences externalPreferences) { | ||
| Study study = buildStudy(); | ||
| if (study.getDatabases().isEmpty()) { | ||
| dialogService.notify(Localization.lang("Please select at least one catalog.")); | ||
| return; | ||
| } | ||
|
|
||
| DirectoryDialogConfiguration config = new DirectoryDialogConfiguration.Builder() | ||
| .withInitialDirectory(initialDirectory) | ||
| .build(); | ||
|
|
||
| dialogService.showDirectorySelectionDialog(config).ifPresent(exportDirectory -> { | ||
| try { | ||
| new SearchRxivExporter().export(study, exportDirectory); | ||
| NativeDesktop.openBrowserShowPopup( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still not sure if we want to open a website after exporting the search queries to JSON. @koppor @calixtus @subhramit what do you think?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is partially known behaviour, if I export something, I open the browser to show the result of the export. If it's just Json exportet, I wouldn't show the result, its just technical.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn’t really find the button “use json search” on the website, so I think it’s better not to show |
||
| SearchRxivExporter.SEARCHRXIV_URL, | ||
| dialogService, | ||
| externalPreferences); | ||
| dialogService.notify(Localization.lang("Exported search queries for SearchRxiv.")); | ||
| } catch (IOException e) { | ||
| LOGGER.error("Could not export search queries for SearchRxiv", e); | ||
| dialogService.notify(Localization.lang("Could not export search queries.")); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package org.jabref.logic.exporter; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
|
|
||
| import org.jabref.model.study.Study; | ||
| import org.jabref.model.study.StudyDatabase; | ||
| import org.jabref.model.study.StudyQuery; | ||
|
|
||
| import tools.jackson.databind.ObjectMapper; | ||
|
|
||
| /// Exports study search queries in the SearchRxiv / search-query JSON format. | ||
| /// One file is created per query and database combination, | ||
| /// as the format uses a single platform string per record. | ||
| /// | ||
| /// The JSON format follows the search-query library's SearchFile specification. | ||
| /// | ||
| /// @see <a href="https://github.com/CoLRev-Environment/search-query">search-query library</a> | ||
| /// @see <a href="https://colrev-environment.github.io/search-query/">search-query format documentation</a> | ||
| /// @see <a href="https://www.cabidigitallibrary.org/journal/searchrxiv">SearchRxiv</a> | ||
| public class SearchRxivExporter { | ||
|
|
||
| public static final String SEARCHRXIV_URL = "https://www.cabidigitallibrary.org/journal/searchrxiv"; | ||
|
|
||
| private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
|
|
||
| /// Exports the study's search queries as JSON files to the given directory. | ||
| /// One file per query and database combination is created. | ||
| /// | ||
| /// @param study The study containing queries, databases, and authors | ||
| /// @param directory The target directory to write the JSON files into | ||
| /// @throws IOException if a file cannot be written | ||
| public void export(Study study, Path directory) throws IOException { | ||
| int index = 0; | ||
| for (StudyQuery studyQuery : study.getQueries()) { | ||
| for (StudyDatabase database : study.getDatabases()) { | ||
| Path file = directory.resolve(buildFileName(studyQuery.getQuery(), database.getName(), index)); | ||
| Files.writeString(file, buildJson(study, studyQuery.getQuery(), database.getName())); | ||
| index++; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Builds a JSON string following the search-query format. | ||
| /// Format spec: <a href="https://github.com/CoLRev-Environment/search-query">search-query</a> | ||
| private String buildJson(Study study, String query, String platform) throws IOException { | ||
| Map<String, Object> data = new LinkedHashMap<>(); | ||
| data.put("search_string", query); | ||
| data.put("platform", platform.toLowerCase()); | ||
| data.put("authors", study.getAuthors().stream() | ||
| .map(author -> Map.of("name", author)) | ||
| .toList()); | ||
| // Placeholder fields — to be filled by the user on SearchRxiv | ||
| data.put("record_info", Map.of()); | ||
| data.put("date", Map.of()); | ||
| return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(data); | ||
| } | ||
|
|
||
| private String buildFileName(String query, String databaseName, int index) { | ||
| String cleanDb = databaseName.replaceAll("[^A-Za-z0-9]", "_"); | ||
| String cleanQuery = query.replaceAll("[^A-Za-z0-9]", "_"); | ||
| if (cleanQuery.isEmpty()) { | ||
| cleanQuery = "query"; | ||
| } | ||
| if (cleanQuery.length() > 20) { | ||
| cleanQuery = cleanQuery.substring(0, 20); | ||
| } | ||
| return cleanDb + "-" + cleanQuery + "-" + index + ".json"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -245,14 +245,15 @@ Error\ during\ persistence\ of\ crawling\ results.=Error during persistence of c | |
| '%0'\ exists.\ Overwrite\ file?='%0' exists. Overwrite file? | ||
| Export=Export | ||
| Export\ to\ clipboard=Export to clipboard | ||
| Export\ search\ queries\ (search-query\ format)=Export search queries (search-query format) | ||
| Export\ search\ queries\ as\ search-query\ JSON\ files\ and\ open\ SearchRxiv=Export search queries as search-query JSON files and open SearchRxiv | ||
| Exported\ search\ queries\ for\ SearchRxiv.=Exported search queries for SearchRxiv. | ||
| Could\ not\ export\ search\ queries.=Could not export search queries. | ||
|
|
||
| Extract\ related\ work\ comments=Extract related work comments | ||
| Extract\ references\ from\ file\ (offline)=Extract references from file (offline) | ||
| Extract\ references\ from\ file\ (online)=Extract references from file (online) | ||
| Extract\ References\ (offline)=Extract References (offline) | ||
| Extract\ References\ (online)=Extract References (online) | ||
| Insert\ related\ work\ comments=Insert related work comments | ||
| Insert\ related\ work\ text=Insert related work text | ||
|
|
||
| Processing...=Processing... | ||
| Processing\ "%0"...=Processing "%0"... | ||
|
|
@@ -1966,7 +1967,6 @@ Could\ not\ load\ PDF\:\ %0=Could not load PDF: %0 | |
| Loading\ PDF...=Loading PDF... | ||
| No\ PDF\ available\ for\ preview=No PDF available for preview | ||
| No\ PDF\ files\ available=No PDF files available | ||
| Please\ attach\ PDF\ files=Please attach PDF files | ||
|
|
||
| Live=Live | ||
| Locked=Locked | ||
|
|
@@ -2158,17 +2158,6 @@ No\ active\ entry=No active entry | |
| LaTeX\ citations=LaTeX citations | ||
| Search\ for\ citations\ in\ LaTeX\ files...=Search for citations in LaTeX files... | ||
| LaTeX\ Citations\ Search\ Results=LaTeX Citations Search Results | ||
| PDF\ file=PDF file | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, but why are there so many deletions? I think the feature only introduces new strings, or not?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The deletions in JabRef_en.properties are from a bad merge, not intentional, i will fix it. |
||
| Comment\ owner=Comment owner | ||
| Source\ entry=Source entry | ||
| Related\ work\ text=Related work text | ||
| Matched\ references=Matched references | ||
| Citation\ marker=Citation marker | ||
| Comment\ preview=Comment preview | ||
| Insert=Insert | ||
| No\ citations\ were\ found\ in\ the\ related\ work\ text.=No citations were found in the related work text. | ||
| No\ matching\ references\ were\ found.=No matching references were found. | ||
| Processed\ related\ work\ comments.\ Inserted\:\ %0,\ unchanged\:\ %1.=Processed related work comments. Inserted: %0, unchanged: %1. | ||
| LaTeX\ files\ directory\:=LaTeX files directory: | ||
| LaTeX\ files\ found\:=LaTeX files found: | ||
| Search\ citations\ for\ this\ entry\ in\ LaTeX\ files=Search citations for this entry in LaTeX files | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.