Skip to content

Commit

Permalink
Merge pull request #466 from FluxNotes/change-note-searching
Browse files Browse the repository at this point in the history
Scroll to open note search results; highlight closed clinical note search results on hover
  • Loading branch information
misohappi authored Oct 1, 2018
2 parents 0fdaab7 + ad9bec6 commit 9749eb8
Show file tree
Hide file tree
Showing 28 changed files with 584 additions and 337 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ yarn-error.log*
/src/summary/NarrativeNameValuePairsVisualizer.css
/src/timeline/Timeline.css
/src/timeline/TimelineEventsVisualizer.css
/src/patientControl/SearchSuggestion.css

#Visual Paradigm Project Files
*.vpp.bak*
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
"d3-scale": "^1.0.7",
"d3-time": "^1.0.8",
"downshift": "^1.30.1",
"elasticlunr": "^0.9.5",
"es6-shim": "0.35.3",
"fhirclient": "^0.1.12",
"flat": "^4.1.0",
"flux_notes_treatment_options_rest_client": "^1.0.0",
"font-awesome": "4.7.0",
"fuse.js": "^3.2.1",
"guid": "0.0.12",
"highcharts": "^6.1.1",
"history": "^4.7.2",
Expand Down
59 changes: 54 additions & 5 deletions src/notes/FluxNotesEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import NLPHashtagPlugin from './NLPHashtagPlugin'
import NoteParser from '../noteparser/NoteParser';
import './FluxNotesEditor.css';
import { setTimeout } from 'timers';
import NoteContentIndexer from '../patientControl/NoteContentIndexer';

// This forces the initial block to be inline instead of a paragraph. When insert structured field, prevents adding new lines
const initialState = Slate.Plain.deserialize('');
Expand Down Expand Up @@ -89,6 +90,8 @@ class FluxNotesEditor extends React.Component {
this.plugins = [];
this.previousState = {};

this.noteContentIndexer = new NoteContentIndexer();

// Set the initial state when the app is first constructed.
this.resetEditorState();

Expand Down Expand Up @@ -511,6 +514,7 @@ class FluxNotesEditor extends React.Component {
}

closeNote = () => {
this.props.searchIndex.removeDataBySection('Open Note');
if (this.props.noteAssistantMode === 'pick-list-options-panel') {
const documentText = this.getNoteText(this.previousState);
this.revertTemplate();
Expand Down Expand Up @@ -626,7 +630,7 @@ class FluxNotesEditor extends React.Component {
return transform;
}

scrollToShortcut = (document, shortcutKey) => {
scrollToData = (document, shortcutKey) => {
const node = document.getNode(shortcutKey);

try {
Expand All @@ -644,7 +648,7 @@ class FluxNotesEditor extends React.Component {
if (nextProps.shouldUpdateShortcutType) {
let transform = this.state.state.transform();
let state = transform.setNodeByKey(nextProps.shortcutKey, nextProps.shortcutType).apply();
this.scrollToShortcut(state.document, nextProps.shortcutKey);
this.scrollToData(state.document, nextProps.shortcutKey);
this.setState({ state });
}
nextProps.selectedPickListOptions.forEach(picklist => {
Expand Down Expand Up @@ -678,7 +682,7 @@ class FluxNotesEditor extends React.Component {
transform = this.resetShortcutData(shortcut, transform);
let state = transform.apply();
this.setState({ state }, () => {
this.scrollToShortcut(state.document, shortcut.getKey());
this.scrollToData(state.document, shortcut.getKey());
});
}
}
Expand Down Expand Up @@ -737,6 +741,19 @@ class FluxNotesEditor extends React.Component {
this.props.setNoteViewerEditable(true);
}
}
this.props.searchIndex.removeDataBySection('Open Note');
const sectionId = nextProps.updatedEditorNote.signed ? 'signed_notes' : 'in_progress_notes';
this.props.searchIndex.addSearchableData({
id: 'open_note_section',
section: 'Open Note',
subsection: '',
valueTitle: 'Section',
value: 'Open Note',
onHighlight: null,
onClick: null
});
this.noteContentIndexer.indexData("Open Note", '', nextProps.updatedEditorNote, this.props.searchIndex, this.onOpenNoteSearchResultHighlight, null);
this.props.searchIndex.removeDataByRef(`clinical_notes_${sectionId}_content_${nextProps.updatedEditorNote.entryInfo.entryId}`);
}

// Check if the current view mode changes
Expand Down Expand Up @@ -769,12 +786,44 @@ class FluxNotesEditor extends React.Component {
const shortcutKey = this.structuredFieldMapManager.getKeyFromEntryId(nextProps.openSourceNoteEntryId);

if (shortcutKey) {
this.scrollToShortcut(this.state.state.document, shortcutKey);
this.scrollToData(this.state.state.document, shortcutKey);
this.props.setOpenSourceNoteEntryId(null);
}
}
}

onOpenNoteSearchResultHighlight = (suggestion) => {
const {document} = this.state.state;
let startIndex = suggestion.indices[0];
let foundNode;
document.toJSON().nodes.find(node => {
const nodeLength = this.getLengthOfNode(node);
if (startIndex <= nodeLength) {
foundNode = node;
return true;
} else {
startIndex -= nodeLength;
return false;
}
});
if (foundNode) this.scrollToData(document, foundNode.key);
}

getLengthOfNode = (node) => {
let length = 0;
if (node.type === 'line') {
node.nodes.forEach(node => {
length += this.getLengthOfNode(node);
});
} else if (node.characters) {
length += node.characters.length;
} else if (node.type === 'structured_field') {
let shortcut = node.data.shortcut;
length += shortcut.getText().length;
}
return length;
}

revertTemplate = () => {
this.props.changeShortcutType(null, false, null);
this.setState({ state: this.previousState }, () => {
Expand Down Expand Up @@ -1126,7 +1175,7 @@ class FluxNotesEditor extends React.Component {

if (shortcutKey) {
setTimeout(() => {
this.scrollToShortcut(state.document, shortcutKey)
this.scrollToData(state.document, shortcutKey)
this.props.setOpenSourceNoteEntryId(null);
}, 0);
}
Expand Down
37 changes: 31 additions & 6 deletions src/notes/NoteAssistant.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export default class NoteAssistant extends Component {
// insertingTemplate indicates whether a shortcut or template is being inserted
// false indicates a shortcut is being inserted
// true indicates a template is being inserted
insertingTemplate: false
insertingTemplate: false,
searchResultNoteId: null
};
// On creating of NoteAssistant, check if the note viewer is editable
if (this.props.isNoteViewerEditable) {
Expand Down Expand Up @@ -217,16 +218,39 @@ export default class NoteAssistant extends Component {
}

deleteSelectedNote = () => {
this.props.searchIndex.removeDataBySection('Open Note');
this.props.deleteSelectedNote();
}

onSearchSuggestionHighlighted = (suggestion, shouldReset=false) => {
this.toggleView('clinical-notes');
if(shouldReset) {
this.setState({
searchResultNoteId: null
});
} else {
this.setState({
searchResultNoteId: suggestion.note.entryInfo.entryId
}, () => {
const domNodeRef = this.refs[suggestion.note.entryInfo.entryId];
if (domNodeRef && domNodeRef.scrollIntoView) {
domNodeRef.scrollIntoView();
}
});
}
}

onSearchSuggestionClicked = (suggestion) => {
this.openNote(!suggestion.note.signed, suggestion.note);
}

// Render the content for the Note Assistant panel
renderNoteAssistantContent(noteAssistantMode) {
const allNotes = this.props.patient.getNotes();
const numberOfPreviousSignedNotes = Lang.filter(allNotes, o => o.signed).length;
const notesIndexer = new NotesIndexer();
this.props.searchIndex.removeDataBySection('Clinical Notes');
notesIndexer.indexData('Clinical Notes', '', allNotes, this.props.searchIndex);
// Temporarily disabling opening source note on click
notesIndexer.indexData('Clinical Notes', '', allNotes, this.props.searchIndex, this.onSearchSuggestionHighlighted, null); //this.onSearchSuggestionClicked
switch (noteAssistantMode) {
case "poc":
return (
Expand Down Expand Up @@ -315,13 +339,14 @@ export default class NoteAssistant extends Component {

renderInProgressNote(note, i) {
let selected = Lang.isEqual(this.props.selectedNote, note);
let searchedFor = note.entryInfo.entryId === this.state.searchResultNoteId;
// if we have closed the note, selected = false
if (Lang.isEqual(this.props.noteClosed, true)) {
selected = false;
}

return (
<div ref={note.entryInfo.entryId} className={`note in-progress-note${selected ? " selected" : ""}`} key={i} onClick={() => {
<div ref={note.entryInfo.entryId} className={`note in-progress-note${selected ? " selected" : ""}${searchedFor ? " search-result" : ""}`} key={i} onClick={() => {
this.openNote(true, note)
}}>
<div className="in-progress-text">In progress note</div>
Expand Down Expand Up @@ -383,14 +408,14 @@ export default class NoteAssistant extends Component {
// For each clinical note, render the note image with the text
renderClinicalNote(item, i) {
let selected = Lang.isEqual(this.props.selectedNote, item);
let searchedFor = item.entryInfo.entryId === this.state.searchResultNoteId;
// if we have closed the note, selected = false
if (Lang.isEqual(this.props.noteClosed, true)) {
selected = false;
}

return (
<div ref={item.entryInfo.entryId} className={`note existing-note${selected ? " selected" : ""}`} key={i} onClick={() => {

<div ref={item.entryInfo.entryId} className={`note existing-note${selected ? " selected" : ""}${searchedFor ? " search-result" : ""}`} key={i} onClick={() => {
this.openNote(false, item)
}}>
<div className="existing-note-date">{item.signedOn}</div>
Expand Down
4 changes: 4 additions & 0 deletions src/notes/NoteAssistant.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
$note-assitant-width-1: 175px;
$note-assitant-width-2: 170px;
$note-assitant-width-3: 160px;
$highlight-yellow: rgb(255, 255, 70);

/* Wrapper */
.note-assistant-wrapper {
Expand Down Expand Up @@ -108,6 +109,9 @@ $note-assitant-width-3: 160px;
border-width: 2px;
border-color: $interface-blue;
}
&.search-result {
background-color: $highlight-yellow;
}
&.existing-note {
.existing-note-date {
padding-bottom: 5px;
Expand Down
8 changes: 8 additions & 0 deletions src/panels/NotesPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@ export default class NotesPanel extends Component {
this.props.patient.reAddEntryToPatient(b);
});

this.props.searchIndex.removeDataBySection('Open Note');
this.props.searchIndex.removeDataByRef(`clinical_notes_in_progress_notes_created_by_${tempNote.entryInfo.entryId}`);
this.props.searchIndex.removeDataByRef(`clinical_notes_in_progress_notes_created_on_${tempNote.entryInfo.entryId}`);
this.props.searchIndex.removeDataByRef(`clinical_notes_in_progress_notes_title_${tempNote.entryInfo.entryId}`);
this.props.searchIndex.removeDataByRef(`clinical_notes_in_progress_notes_content_${tempNote.entryInfo.entryId}`);
this.props.searchIndex.removeDataByRef(`clinical_notes_in_progress_notes_source_${tempNote.entryInfo.entryId}`);

// Close the current note
this.closeNote();
}
Expand Down Expand Up @@ -396,6 +403,7 @@ export default class NotesPanel extends Component {
changeShortcutType={this.changeShortcutType}
openSourceNoteEntryId={this.props.openSourceNoteEntryId}
setOpenSourceNoteEntryId={this.props.setOpenSourceNoteEntryId}
searchIndex={this.props.searchIndex}
/>
</div>
);
Expand Down
5 changes: 5 additions & 0 deletions src/panels/TargetedDataPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export default class TargetedDataPanel extends Component {
return this.minimap.moveToSubsection(sectionName, subsectionName);
}

moveToSubsectionFromSearch(suggestion) {
this.moveToSubsection(suggestion.section, suggestion.subsection)
}

render () {
// The css data attribute associated with the minimap
const minimapAttribute = 'data-test-summary-section';
Expand Down Expand Up @@ -51,6 +55,7 @@ export default class TargetedDataPanel extends Component {
setForceRefresh={this.props.setForceRefresh}
summaryMetadata={this.props.summaryMetadata.getMetadata()}
searchIndex={this.props.searchIndex}
moveToSubsectionFromSearch={this.moveToSubsectionFromSearch.bind(this)}
/>
</div>
</div>
Expand Down
12 changes: 10 additions & 2 deletions src/patientControl/BaseIndexer.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { Component } from 'react';

class BaseIndexer extends Component {
indexData(section, subsection, data, searchIndex) {
indexData(section, subsection, data, searchIndex, onHighlight) {
if (subsection) {
const sectionId = this.getStringForId(section);
const subsectionId = this.getStringForId(subsection);
searchIndex.addSearchableData({
id: `${sectionId}_${subsectionId}_subsection`,
section,
subsection,
valueTitle: "Subsection",
value: subsection
value: subsection,
onHighlight
});
}
}

getStringForId(s) {
return s.toLowerCase().replace(/[.,#!$%&;:{}=\-_`~()]/g,"").replace(/ /g, '_');
}
}

export default BaseIndexer;
4 changes: 2 additions & 2 deletions src/patientControl/ClusterPointsIndexer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import BaseIndexer from './BaseIndexer';

class ClusterPointsIndexer extends BaseIndexer {
indexData(section, subsection, data, searchIndex) {
super.indexData(section, subsection, data, searchIndex);
indexData(section, subsection, data, searchIndex, onHighlight) {
super.indexData(section, subsection, data, searchIndex, onHighlight);
}
}

Expand Down
21 changes: 15 additions & 6 deletions src/patientControl/ColumnsIndexer.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import BaseIndexer from './BaseIndexer';

class ColumnIndexer extends BaseIndexer {
indexData(section, subsection, data, searchIndex, subsectionDescriptor) {
super.indexData(section, subsection, data, searchIndex);
indexData(section, subsection, data, searchIndex, onHighlight, subsectionDescriptor) {
super.indexData(section, subsection, data, searchIndex, onHighlight);
const sectionId = super.getStringForId(section);
const subsectionId = super.getStringForId(subsection);
if (subsectionDescriptor.headings) {
// true tabular where each item is a column of data
// add each column value using title of the column heading
data.forEach((row) => {
data.forEach((row, rowNumber) => {
row.forEach((col, columnNumber) => {
const vtPrefix = columnNumber === 0 ? '' : `${row[0].value} `;
searchIndex.addSearchableData({
id: `${sectionId}_${subsectionId}_${rowNumber}_${columnNumber}`,
section,
subsection,
valueTitle: `${vtPrefix}${subsectionDescriptor.headings[columnNumber]}`,
value: col.value || "Missing Data"
value: col.value || "Missing Data",
onHighlight
});
});
});
Expand All @@ -24,18 +28,23 @@ class ColumnIndexer extends BaseIndexer {
if (row.length < 2) {
const [ valueObject ] = row;
searchIndex.addSearchableData({
id: `${sectionId}_${subsectionId}_${subsectionId}`,
section,
subsection,
valueTitle: subsection,
value: valueObject.value || "Missing Data"
value: valueObject.value || "Missing Data",
onHighlight
});
} else {
const [ title, valueObject ] = row;
const valueTitleId = super.getStringForId(title.value);
searchIndex.addSearchableData({
id: `${sectionId}_${valueTitleId}`,
section,
subsection,
valueTitle: title.value,
value: valueObject.value || "Missing Data"
value: valueObject.value || "Missing Data",
onHighlight
});
}
});
Expand Down
Loading

0 comments on commit 9749eb8

Please sign in to comment.