diff --git a/src/org/ohdsi/usagi/UsagiSearchEngine.java b/src/org/ohdsi/usagi/UsagiSearchEngine.java index fd0c55f..84892f0 100644 --- a/src/org/ohdsi/usagi/UsagiSearchEngine.java +++ b/src/org/ohdsi/usagi/UsagiSearchEngine.java @@ -279,7 +279,7 @@ public int getTermCount() { } public List search(String searchTerm, boolean useMlt, Collection filterConceptIds, Vector filterDomains, Vector filterConceptClasses, - Vector filterVocabularies, boolean filterStandard, boolean includeSourceConcepts) { + Vector filterVocabularies, boolean filterStandard, boolean includeSourceConcepts) { List results = new ArrayList(); try { Query query; @@ -406,7 +406,7 @@ else if (arg1.term.toLowerCase().equals(arg1.concept.conceptName.toLowerCase())) /** * Lucene's matching score does some weird things: it is not normalized (the value can be greater than 1), and not all tokens are included in the * computation. For that reason, we're recomputing the matching score as plain TF*IDF cosine matching here. - * + * * @param scoreDocs * The array of documents scored by Lucene * @param query @@ -438,6 +438,39 @@ public int compare(ScoreDoc arg0, ScoreDoc arg1) { } } + public List searchConceptSynonymsByConceptId(int conceptId) { + return searchTermsByConceptId(conceptId, CONCEPT_TERM); + } + + public List searchSourceSynonymsByConceptId(int conceptId) { + return searchTermsByConceptId(conceptId, SOURCE_TERM); + } + + private List searchTermsByConceptId(int conceptId, String termType) { + List result = new ArrayList<>(); + try { + QueryParser keywordsQueryParser = new QueryParser(Version.LUCENE_4_9, "CONCEPT_ID", new KeywordAnalyzer()); + Query conceptIdQuery = keywordsQueryParser.parse(String.valueOf(conceptId)); + BooleanQuery booleanQuery = new BooleanQuery(); + booleanQuery.add(conceptIdQuery, Occur.MUST); + + QueryParser termTypeQueryParser = new QueryParser(Version.LUCENE_4_9, "TERM_TYPE", new KeywordAnalyzer()); + Query termTypeQuery = termTypeQueryParser.parse(termType); + booleanQuery.add(termTypeQuery, Occur.MUST); + + TopDocs topDocs = searcher.search(booleanQuery, 100); + for (ScoreDoc scoreDoc : topDocs.scoreDocs) { + Document document = reader.document(scoreDoc.doc); + String term = document.get("TERM"); + result.add(term); + } + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(); + } + return result; + } + public static class ScoredConcept { public float matchScore; public Concept concept; diff --git a/src/org/ohdsi/usagi/ui/ConceptInformationDialog.java b/src/org/ohdsi/usagi/ui/ConceptInformationDialog.java index 075cc21..e7d6fa6 100644 --- a/src/org/ohdsi/usagi/ui/ConceptInformationDialog.java +++ b/src/org/ohdsi/usagi/ui/ConceptInformationDialog.java @@ -20,26 +20,11 @@ import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JTabbedPane; -import javax.swing.JTable; -import javax.swing.JTextArea; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; +import javax.swing.*; import javax.swing.table.TableRowSorter; import org.ohdsi.usagi.Concept; @@ -49,17 +34,19 @@ public class ConceptInformationDialog extends JFrame { private static final long serialVersionUID = -2112565437136224217L; - private JTextArea area; + private Concept activeConcept; private JLabel conceptNameLabel; private JButton backButton; private JButton forwardButton; private ConceptTableModel parentConceptTableModel; private UsagiTable parentsConceptTable; + private ConceptTableModel currentConceptTableModel; private ConceptTableModel childrenConceptTableModel; private UsagiTable childrenConceptTable; private ConceptTableModel sourceConceptTableModel; - private UsagiTable sourceConceptTable; - private List history = new ArrayList(); + private JTextArea conceptSynonymArea; + private JTextArea sourceSynonymArea; + private List history = new ArrayList<>(); private int historyCursor = -1; private boolean updating = false; @@ -78,10 +65,8 @@ private Component createCenterPanel() { JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createEmptyBorder()); panel.setLayout(new BorderLayout()); - JScrollPane infoPanel = createInfoPanel(); JTabbedPane tabPanel = createTabPanel(); - JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, infoPanel, tabPanel); - panel.add(splitPane, BorderLayout.CENTER); + panel.add(tabPanel, BorderLayout.CENTER); return panel; } @@ -89,21 +74,13 @@ private JTabbedPane createTabPanel() { JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("Hierarchy", createHierarchyPanel()); tabbedPane.addTab("Source concepts", createSourceConceptPanel()); + tabbedPane.addTab("Synonyms", createSynonymsPanel()); return tabbedPane; } private Component createSourceConceptPanel() { sourceConceptTableModel = new ConceptTableModel(false); - sourceConceptTable = new UsagiTable(sourceConceptTableModel); - sourceConceptTable.setPreferredScrollableViewportSize(new Dimension(500, 45)); - sourceConceptTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - sourceConceptTable.setRowSelectionAllowed(false); - sourceConceptTable.setRowSorter(new TableRowSorter(sourceConceptTableModel)); - sourceConceptTable.hideColumn("Parents"); - sourceConceptTable.hideColumn("Children"); - sourceConceptTable.hideColumn("Valid start date"); - sourceConceptTable.hideColumn("Valid end date"); - sourceConceptTable.hideColumn("Invalid reason"); + UsagiTable sourceConceptTable = buildConceptTable(sourceConceptTableModel, false); JScrollPane sourcePane = new JScrollPane(sourceConceptTable); sourcePane.setBorder(BorderFactory.createTitledBorder("Source concepts")); @@ -121,82 +98,109 @@ private Component createHierarchyPanel() { c.weightx = 1; parentConceptTableModel = new ConceptTableModel(false); - parentsConceptTable = new UsagiTable(parentConceptTableModel); - parentsConceptTable.setRowSorter(new TableRowSorter(parentConceptTableModel)); - parentsConceptTable.setPreferredScrollableViewportSize(new Dimension(500, 45)); - parentsConceptTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - parentsConceptTable.setRowSelectionAllowed(true); - parentsConceptTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - parentsConceptTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent event) { - if (!updating) { - updating = true; - int viewRow = parentsConceptTable.getSelectedRow(); - Global.conceptInfoAction.setEnabled(true); - int modelRow = parentsConceptTable.convertRowIndexToModel(viewRow); - Global.conceptInformationDialog.setConcept(parentConceptTableModel.getConcept(modelRow)); - updating = false; - } + parentsConceptTable = buildConceptTable(parentConceptTableModel, true); + parentsConceptTable.getSelectionModel().addListSelectionListener(event -> { + if (!updating) { + updating = true; + int viewRow = parentsConceptTable.getSelectedRow(); + Global.conceptInfoAction.setEnabled(true); + int modelRow = parentsConceptTable.convertRowIndexToModel(viewRow); + Global.conceptInformationDialog.setActiveConcept(parentConceptTableModel.getConcept(modelRow)); + updating = false; } }); - parentsConceptTable.hideColumn("Parents"); - parentsConceptTable.hideColumn("Children"); - parentsConceptTable.hideColumn("Valid start date"); - parentsConceptTable.hideColumn("Valid end date"); - parentsConceptTable.hideColumn("Invalid reason"); JScrollPane parentsPane = new JScrollPane(parentsConceptTable); parentsPane.setBorder(BorderFactory.createTitledBorder("Parent concepts")); parentsPane.setMinimumSize(new Dimension(500, 50)); parentsPane.setPreferredSize(new Dimension(500, 50)); c.gridy = 0; - c.weighty = 0.4; + c.weighty = 0.3; panel.add(parentsPane, c); + currentConceptTableModel = new ConceptTableModel(false); + UsagiTable currentConceptTable = buildConceptTable(currentConceptTableModel, false); + JScrollPane currentConceptPane = new JScrollPane(currentConceptTable); + currentConceptPane.setBorder(BorderFactory.createTitledBorder("Current concept")); + currentConceptPane.setMinimumSize(new Dimension(500, 20)); + currentConceptPane.setPreferredSize(new Dimension(500, 20)); + c.gridy = 1; + c.weighty = 0.2; + panel.add(currentConceptPane, c); + childrenConceptTableModel = new ConceptTableModel(false); - childrenConceptTable = new UsagiTable(childrenConceptTableModel); - childrenConceptTable.setRowSorter(new TableRowSorter(childrenConceptTableModel)); - childrenConceptTable.setPreferredScrollableViewportSize(new Dimension(500, 45)); - childrenConceptTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - childrenConceptTable.setRowSelectionAllowed(true); - childrenConceptTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - childrenConceptTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent event) { - if (!updating) { - updating = true; - int viewRow = childrenConceptTable.getSelectedRow(); - Global.conceptInfoAction.setEnabled(true); - int modelRow = childrenConceptTable.convertRowIndexToModel(viewRow); - Global.conceptInformationDialog.setConcept(childrenConceptTableModel.getConcept(modelRow)); - updating = false; - } + childrenConceptTable = buildConceptTable(childrenConceptTableModel, true); + childrenConceptTable.getSelectionModel().addListSelectionListener(event -> { + if (!updating) { + updating = true; + int viewRow = childrenConceptTable.getSelectedRow(); + Global.conceptInfoAction.setEnabled(true); + int modelRow = childrenConceptTable.convertRowIndexToModel(viewRow); + Global.conceptInformationDialog.setActiveConcept(childrenConceptTableModel.getConcept(modelRow)); + updating = false; } }); - childrenConceptTable.hideColumn("Parents"); - childrenConceptTable.hideColumn("Children"); - childrenConceptTable.hideColumn("Valid start date"); - childrenConceptTable.hideColumn("Valid end date"); - childrenConceptTable.hideColumn("Invalid reason"); JScrollPane childrenPane = new JScrollPane(childrenConceptTable); childrenPane.setBorder(BorderFactory.createTitledBorder("Children concepts")); childrenPane.setMinimumSize(new Dimension(500, 50)); childrenPane.setPreferredSize(new Dimension(500, 50)); - c.gridy = 1; - c.weighty = 0.6; + c.gridy = 2; + c.weighty = 0.5; panel.add(childrenPane, c); return panel; } - private JScrollPane createInfoPanel() { - area = new JTextArea(); - area.setEditable(false); - JScrollPane scrollPane = new JScrollPane(area); - scrollPane.setBorder(BorderFactory.createTitledBorder("Concept information")); - scrollPane.setPreferredSize(new Dimension(600, 200)); - scrollPane.setMinimumSize(new Dimension(200, 100)); - scrollPane.setAutoscrolls(true); - return scrollPane; + private static UsagiTable buildConceptTable(ConceptTableModel tableModel, boolean rowSelectionAllowed) { + UsagiTable result = new UsagiTable(tableModel); + result.setRowSorter(new TableRowSorter<>(tableModel)); + result.setPreferredScrollableViewportSize(new Dimension(500, 45)); + result.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + result.hideColumn("Parents"); + result.hideColumn("Children"); + result.hideColumn("Valid start date"); + result.hideColumn("Valid end date"); + result.hideColumn("Invalid reason"); + + result.setRowSelectionAllowed(rowSelectionAllowed); + if (rowSelectionAllowed) { + result.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + return result; + } + + private Component createSynonymsPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createEmptyBorder()); + panel.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + + conceptSynonymArea = new JTextArea(); + conceptSynonymArea.setEditable(false); + + JScrollPane conceptSynonymPane = new JScrollPane(conceptSynonymArea); + conceptSynonymPane.setBorder(BorderFactory.createTitledBorder("Concept Synonyms")); + conceptSynonymPane.setMinimumSize(new Dimension(500, 50)); + conceptSynonymPane.setPreferredSize(new Dimension(500, 50)); + c.gridy = 0; + c.weighty = 0.5; + panel.add(conceptSynonymPane, c); + + sourceSynonymArea = new JTextArea(); + sourceSynonymArea.setEditable(false); + + JScrollPane sourceSynonymPane = new JScrollPane(sourceSynonymArea); + sourceSynonymPane.setBorder(BorderFactory.createTitledBorder("Source Synonyms")); + sourceSynonymPane.setMinimumSize(new Dimension(500, 50)); + sourceSynonymPane.setPreferredSize(new Dimension(500, 50)); + c.gridy = 1; + c.weighty = 0.5; + panel.add(sourceSynonymPane, c); + + return panel; } private JPanel createButtonPanel() { @@ -206,25 +210,17 @@ private JPanel createButtonPanel() { JButton replaceButton = new JButton("Replace concept"); replaceButton.setToolTipText("Replace selected concept"); - replaceButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - Global.mappingDetailPanel.replaceConcepts(history.get(historyCursor)); - Global.frame.requestFocus(); - } - + replaceButton.addActionListener(e -> { + Global.mappingDetailPanel.replaceConcepts(history.get(historyCursor)); + Global.frame.requestFocus(); }); - // replaceButton.setEnabled(false); buttonPanel.add(replaceButton); JButton addButton = new JButton("Add concept"); addButton.setToolTipText("Add selected concept"); - addButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - Global.mappingDetailPanel.addConcept(history.get(historyCursor)); - Global.frame.requestFocus(); - } - + addButton.addActionListener(e -> { + Global.mappingDetailPanel.addConcept(history.get(historyCursor)); + Global.frame.requestFocus(); }); - // addButton.setEnabled(false); buttonPanel.add(addButton); return buttonPanel; } @@ -234,28 +230,16 @@ private Component createHeaderPanel() { panel.setBorder(BorderFactory.createEmptyBorder()); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); conceptNameLabel = new JLabel("No concept selected"); - panel.add(conceptNameLabel); panel.add(Box.createHorizontalGlue()); + panel.add(conceptNameLabel); backButton = new JButton("<"); backButton.setToolTipText("Back to previous concept"); - backButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent arg0) { - goBack(); - } - }); + backButton.addActionListener(arg0 -> goBack()); backButton.setEnabled(false); panel.add(backButton); forwardButton = new JButton(">"); forwardButton.setToolTipText("Forward to next concept"); - forwardButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent arg0) { - goForward(); - } - }); + forwardButton.addActionListener(arg0 -> goForward()); forwardButton.setEnabled(false); panel.add(forwardButton); return panel; @@ -279,7 +263,10 @@ private void goForward() { showConcept(concept); } - public void setConcept(Concept concept) { + public void setActiveConcept(Concept concept) { + this.activeConcept = concept; + + // Keep track of active concept history if (historyCursor < 0 || history.get(historyCursor).conceptId != concept.conceptId) { if (historyCursor < history.size() - 1) history = history.subList(0, historyCursor + 1); @@ -287,8 +274,17 @@ public void setConcept(Concept concept) { history.add(concept); backButton.setEnabled(historyCursor > 0); forwardButton.setEnabled(false); - showConcept(concept); } + + if (this.isVisible()) { + showConcept(activeConcept); + } + } + + @Override + public void setVisible(boolean b) { + super.setVisible(b); + this.showConcept(activeConcept); } private void showConcept(Concept concept) { @@ -297,32 +293,35 @@ private void showConcept(Concept concept) { name = name.substring(0, 80) + "..."; conceptNameLabel.setText(name + " (" + concept.conceptId + ")"); - StringBuilder conceptInfo = new StringBuilder(); - conceptInfo.append("Concept name: " + concept.conceptName + "\n"); - conceptInfo.append("Domain ID: " + concept.domainId + "\n"); - conceptInfo.append("Concept class ID: " + concept.conceptClassId + "\n"); - conceptInfo.append("Vocabulary ID: " + concept.vocabularyId + "\n"); - conceptInfo.append("Concept ID: " + concept.conceptId + "\n"); - conceptInfo.append("Concept code: " + concept.conceptCode + "\n"); - conceptInfo.append("Invalid reason: " + concept.invalidReason + "\n"); - conceptInfo.append("Standard concept: " + concept.standardConcept + "\n"); - if (concept.additionalInformation != null) - conceptInfo.append("\n" + concept.additionalInformation.replaceAll("\\\\n", "\n")); - area.setText(conceptInfo.toString()); - - List parents = new ArrayList(); + List parents = new ArrayList<>(); for (ParentChildRelationShip relationship : Global.dbEngine.getParentChildRelationshipsByChildConceptId(concept.conceptId)) parents.add(Global.dbEngine.getConcept(relationship.parentConceptId)); parentConceptTableModel.setConcepts(parents); - List children = new ArrayList(); + currentConceptTableModel.setConcepts(Collections.singletonList(concept)); + + List children = new ArrayList<>(); for (ParentChildRelationShip relationship : Global.dbEngine.getParentChildRelationshipsByParentConceptId(concept.conceptId)) children.add(Global.dbEngine.getConcept(relationship.childConceptId)); childrenConceptTableModel.setConcepts(children); - List sourceConcepts = new ArrayList(); + List sourceConcepts = new ArrayList<>(); for (MapsToRelationship relationship : Global.dbEngine.getMapsToRelationshipsByConceptId2(concept.conceptId)) sourceConcepts.add(Global.dbEngine.getConcept(relationship.conceptId1)); sourceConceptTableModel.setConcepts(sourceConcepts); + + List conceptSynonyms = Global.usagiSearchEngine.searchConceptSynonymsByConceptId(concept.conceptId); + StringBuilder conceptSynonymsText = new StringBuilder(); + for (String synonym : conceptSynonyms) { + conceptSynonymsText.append(synonym).append("\n"); + } + conceptSynonymArea.setText(conceptSynonymsText.toString()); + + List sourceSynonyms = Global.usagiSearchEngine.searchSourceSynonymsByConceptId(concept.conceptId); + StringBuilder sourceSynonymsText = new StringBuilder(); + for (String synonym : sourceSynonyms) { + sourceSynonymsText.append(synonym).append("\n"); + } + sourceSynonymArea.setText(sourceSynonymsText.toString()); } } diff --git a/src/org/ohdsi/usagi/ui/Global.java b/src/org/ohdsi/usagi/ui/Global.java index be488b8..9c70139 100644 --- a/src/org/ohdsi/usagi/ui/Global.java +++ b/src/org/ohdsi/usagi/ui/Global.java @@ -58,6 +58,7 @@ public class Global { public static Vector vocabularyIds; public static Vector domainIds; public static ShowStatsAction showStatsAction; + public static ShowReviewStatsAction showReviewStatsAction; public static String author; } diff --git a/src/org/ohdsi/usagi/ui/MappingDetailPanel.java b/src/org/ohdsi/usagi/ui/MappingDetailPanel.java index c42b06f..5b479ea 100644 --- a/src/org/ohdsi/usagi/ui/MappingDetailPanel.java +++ b/src/org/ohdsi/usagi/ui/MappingDetailPanel.java @@ -21,8 +21,6 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.*; import java.util.Timer; @@ -186,7 +184,7 @@ private Component createSearchResultsPanel() { replaceButton.setEnabled(true); int modelRow = searchTable.convertRowIndexToModel(viewRow); Global.conceptInfoAction.setEnabled(true); - Global.conceptInformationDialog.setConcept(searchTableModel.getConcept(modelRow)); + Global.conceptInformationDialog.setActiveConcept(searchTableModel.getConcept(modelRow)); Global.athenaAction.setEnabled(true); Global.athenaAction.setConcept(searchTableModel.getConcept(modelRow)); Global.googleSearchAction.setEnabled(false); @@ -204,13 +202,10 @@ private Component createSearchResultsPanel() { replaceButton = new JButton("Replace concept"); replaceButton.setToolTipText("Replace selected concept"); - replaceButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - int viewRow = searchTable.getSelectedRow(); - int modelRow = searchTable.convertRowIndexToModel(viewRow); - replaceConcepts(searchTableModel.getConcept(modelRow)); - } - + replaceButton.addActionListener(e -> { + int viewRow = searchTable.getSelectedRow(); + int modelRow = searchTable.convertRowIndexToModel(viewRow); + replaceConcepts(searchTableModel.getConcept(modelRow)); }); replaceButton.setEnabled(false); buttonPanel.add(replaceButton); @@ -231,7 +226,12 @@ public void actionPerformed(ActionEvent e) { }); button.setEnabled(false); addButtons.add(button); - buttonPanel.add(button); + // Add Maps_to button on the right, the other on the left. + if (mappingType.equals(MappingTarget.Type.MAPS_TO)) { + buttonPanel.add(button); + } else { + buttonPanel.add(button, 0); + } } panel.add(buttonPanel); @@ -330,7 +330,7 @@ private JPanel createTargetConceptsPanel() { MappingTarget mappingTarget = targetConceptTableModel.getMappingTarget(modelRow); typesChooser.setSelectedItem(mappingTarget.getMappingType()); Global.conceptInfoAction.setEnabled(true); - Global.conceptInformationDialog.setConcept(mappingTarget.getConcept()); + Global.conceptInformationDialog.setActiveConcept(mappingTarget.getConcept()); Global.athenaAction.setEnabled(true); Global.athenaAction.setConcept(mappingTarget.getConcept()); Global.googleSearchAction.setEnabled(false); @@ -348,7 +348,6 @@ private JPanel createTargetConceptsPanel() { JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); - buttonPanel.add(Box.createHorizontalGlue()); typesChooser = new JComboBox<>(MappingTarget.Type.values()); typesChooser.setToolTipText("Set type of the mapping"); @@ -360,6 +359,8 @@ private JPanel createTargetConceptsPanel() { typesChooser.setEnabled(false); buttonPanel.add(typesChooser); + buttonPanel.add(Box.createHorizontalGlue()); + removeButton = new JButton("Remove concept"); removeButton.setToolTipText("Remove selected concept"); removeButton.addActionListener(e -> remove()); @@ -443,6 +444,11 @@ public void uncheckSelected() { } else { Global.mapping.fireDataChanged(MULTI_UPDATE_EVENT); } + // If a row selected, then enable the add buttons + if (searchTable.getSelectedRow() != -1) { + replaceButton.setEnabled(true); + addButtons.forEach(x -> x.setEnabled(true)); + } } private void toggleStatusButtons() { diff --git a/src/org/ohdsi/usagi/ui/MappingTablePanel.java b/src/org/ohdsi/usagi/ui/MappingTablePanel.java index d69c331..416750e 100644 --- a/src/org/ohdsi/usagi/ui/MappingTablePanel.java +++ b/src/org/ohdsi/usagi/ui/MappingTablePanel.java @@ -31,7 +31,6 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.TableRowSorter; -import org.apache.xmlbeans.impl.xb.ltgfmt.Code; import org.ohdsi.usagi.CodeMapping; import org.ohdsi.usagi.CodeMapping.MappingStatus; import org.ohdsi.usagi.Concept; @@ -73,7 +72,7 @@ public MappingTablePanel() { if (tableModel.getCodeMapping(primaryModelRow).getTargetConcepts().size() > 0) { Concept firstConcept = tableModel.getCodeMapping(primaryModelRow).getTargetConcepts().get(0).getConcept(); Global.conceptInfoAction.setEnabled(true); - Global.conceptInformationDialog.setConcept(firstConcept); + Global.conceptInformationDialog.setActiveConcept(firstConcept); Global.athenaAction.setEnabled(true); Global.athenaAction.setConcept(firstConcept); } @@ -319,6 +318,15 @@ public void clearSelected() { fireUpdateEventAll(MULTI_UPDATE_EVENT); } + public List getSelectedCodeMappings() { + List selectedCodeMappings = new ArrayList<>(); + for (int viewRow : table.getSelectedRows()) { + int modelRow = table.convertRowIndexToModel(viewRow); + selectedCodeMappings.add(tableModel.getCodeMapping(modelRow)); + } + return selectedCodeMappings; + } + public void assignReviewersRandomly(String[] reviewers) { // Randomly assign code mappings to given reviewers ThreadLocalRandom randomGenerator = ThreadLocalRandom.current(); diff --git a/src/org/ohdsi/usagi/ui/ShowReviewStatsDialog.java b/src/org/ohdsi/usagi/ui/ShowReviewStatsDialog.java new file mode 100644 index 0000000..9d6a599 --- /dev/null +++ b/src/org/ohdsi/usagi/ui/ShowReviewStatsDialog.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright 2021 Observational Health Data Sciences and Informatics & The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.ohdsi.usagi.ui; + +import org.ohdsi.usagi.CodeMapping; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ShowReviewStatsDialog extends JDialog { + + private static final long serialVersionUID = -4646761336953654777L; + + private static final Font HEADER_FONT = new Font("Arial", Font.BOLD,12); + + public ShowReviewStatsDialog() { + // If selection of multiple codes made, then use that to calculate statistics + java.util.List selectedCodeMappings = Global.mappingTablePanel.getSelectedCodeMappings(); + List codeMappings; + if (selectedCodeMappings.size() > 1) { + codeMappings = selectedCodeMappings; + } else { + codeMappings = Global.mapping; + } + + setTitle("Review statistics"); + setLayout(new GridBagLayout()); + + GridBagConstraints g = new GridBagConstraints(); + g.fill = GridBagConstraints.BOTH; + g.ipadx = 10; + g.ipady = 10; + + g.gridx = 0; + g.gridy = 0; + addLabel(g, String.format("Number of (selected) source codes: %d", codeMappings.size())); + + // Mapping status + addHeaderLabel(g, "By mapping status:"); + + Map countByMappingStatus = codeMappings.stream() + .collect(Collectors.groupingBy(CodeMapping::getMappingStatus, Collectors.counting())); + + countByMappingStatus.forEach((key, value) -> addLabel(g, String.format("%s - %d", key, value))); + + // Equivalence status + addHeaderLabel(g,"By equivalence status:"); + + Map countByEquivalence = codeMappings.stream() + .collect(Collectors.groupingBy(CodeMapping::getEquivalence, Collectors.counting())); + + countByEquivalence.forEach((key, value) -> addLabel(g, String.format("%s - %d", key, value))); + + // Reviewer + addHeaderLabel(g,"By assigned reviewer:"); + + Map countByAssignedReviewer = codeMappings.stream() + .collect(Collectors.groupingBy(CodeMapping::getAssignedReviewer, Collectors.counting())); + + Map countApprovedByAssignedReviewer = codeMappings.stream() + .filter(x -> x.getMappingStatus().equals(CodeMapping.MappingStatus.APPROVED)) + .collect(Collectors.groupingBy(CodeMapping::getAssignedReviewer, Collectors.counting())); + + countByAssignedReviewer.forEach((key, total) -> { + long nApproved = countApprovedByAssignedReviewer.getOrDefault(key, 0L); + addLabel(g, String.format("%s - %d/%d", key, nApproved, total)); + }); + + // Number of target mappings + addHeaderLabel(g,"By number of target concepts:"); + + Map countByNumberOfTargetConcepts = codeMappings.stream() + .collect(Collectors.groupingBy(x -> x.getTargetConcepts().size(), Collectors.counting())); + + countByNumberOfTargetConcepts.forEach((key, value) -> addLabel(g, String.format("%d - %d", key, value))); + + g.gridx = 0; + g.gridy++; + g.gridwidth = 2; + + JPanel buttonPanel = new JPanel(); + + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(Box.createHorizontalGlue()); + + JButton okButton = new JButton("Ok"); + okButton.addActionListener(arg0 -> close()); + buttonPanel.add(okButton); + buttonPanel.add(Box.createHorizontalGlue()); + + add(buttonPanel, g); + + setModal(true); + setResizable(false); + pack(); + + } + + private JLabel addLabel(GridBagConstraints g, String message) { + g.gridx = 0; + g.gridy++; + JLabel label = new JLabel(message); + add(label, g); + return label; + } + + private JLabel addHeaderLabel(GridBagConstraints g, String message) { + JLabel label = addLabel(g, message); + label.setFont(HEADER_FONT); + return label; + } + + private void close() { + setVisible(false); + } +} diff --git a/src/org/ohdsi/usagi/ui/UsagiMain.java b/src/org/ohdsi/usagi/ui/UsagiMain.java index 7f4f94d..4519742 100644 --- a/src/org/ohdsi/usagi/ui/UsagiMain.java +++ b/src/org/ohdsi/usagi/ui/UsagiMain.java @@ -41,7 +41,7 @@ */ public class UsagiMain implements ActionListener { - public static String version = "1.4.1"; + public static String version = "1.4.2"; public static void main(String[] args) { new UsagiMain(args); @@ -85,6 +85,7 @@ public UsagiMain(String[] args) { Global.athenaAction = new AthenaAction(); Global.googleSearchAction = new GoogleSearchAction(); Global.showStatsAction = new ShowStatsAction(); + Global.showReviewStatsAction = new ShowReviewStatsAction(); Global.aboutAction = new AboutAction(); Global.rebuildIndexAction = new RebuildIndexAction(); Global.exitAction = new ExitAction(); diff --git a/src/org/ohdsi/usagi/ui/UsagiMenubar.java b/src/org/ohdsi/usagi/ui/UsagiMenubar.java index f36ce04..ea03e6f 100644 --- a/src/org/ohdsi/usagi/ui/UsagiMenubar.java +++ b/src/org/ohdsi/usagi/ui/UsagiMenubar.java @@ -59,6 +59,7 @@ public UsagiMenubar() { helpMenu.add(Global.rebuildIndexAction); helpMenu.add(Global.showStatsAction); + helpMenu.add(Global.showReviewStatsAction); helpMenu.add(Global.aboutAction); } diff --git a/src/org/ohdsi/usagi/ui/actions/ShowReviewStatsAction.java b/src/org/ohdsi/usagi/ui/actions/ShowReviewStatsAction.java new file mode 100644 index 0000000..bacba30 --- /dev/null +++ b/src/org/ohdsi/usagi/ui/actions/ShowReviewStatsAction.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright 2021 Observational Health Data Sciences and Informatics & The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.ohdsi.usagi.ui.actions; + +import org.ohdsi.usagi.ui.Global; +import org.ohdsi.usagi.ui.ShowReviewStatsDialog; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class ShowReviewStatsAction extends AbstractAction { + + private static final long serialVersionUID = -5823000156280268511L; + + public ShowReviewStatsAction() { + putValue(Action.NAME, "Show code review statistics"); + putValue(Action.SHORT_DESCRIPTION, "Show review stats"); + putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.ALT_MASK)); + } + + @Override + public void actionPerformed(ActionEvent arg0) { + ShowReviewStatsDialog dialog = new ShowReviewStatsDialog(); + dialog.setLocationRelativeTo(Global.frame); + dialog.setVisible(true); + } +}