Skip to content

Commit

Permalink
Merge pull request #789 from FluxNotes/help-for-missing-parent
Browse files Browse the repository at this point in the history
Helpful message when missing parent context
  • Loading branch information
Matthew Gramigna authored Oct 16, 2019
2 parents c3ee71d + 6c88a51 commit 14ec01c
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 73 deletions.
138 changes: 81 additions & 57 deletions src/context/ContextGetHelp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,85 +108,109 @@ class ContextGetHelp extends React.Component {
}
}

renderOptions() {
// if getHelp is not selected, don't show the additional options
if (this.state.selectedIndex === -1) return null;
renderOptionsWithGetHelp = () => {
// Get name of the shortcut for the getHelp text
const initiatingTrigger = this.props.shortcut.getDisplayText();

return (
<span className="context-get-help-options">
{this.state.getHelpOptions.map((option, index) => {
// the parent 'get help' option is not included in the getHelpOptions array
// but it is included as a selectedIndex, so there is an off by one that needs
// to be calculated, hence the updatedIndex + 1 from the index of the getHelpOptions
const updatedIndex = index + 1;
return (
<li key={updatedIndex}
data-active={this.state.selectedIndex === updatedIndex}
onClick={option.onSelect}
onMouseEnter={() => { this.setSelectedIndex(updatedIndex); }}
>
{option.text}
</li>
);
})}
</span>
);
}
// Determine if we should display anything other than the getHelp option
const isGetHelpClosed = this.state.selectedIndex === -1;
// For any informational flags, define them here and chain them together into a single variable
// This variable will determine if we display a horizontal bar, separating information from actions
const isMissingParent = this.props.shortcut.isMissingParent;
const isInformationAvailable = isMissingParent || false;

// Create our icon class to signal the expanding/collapsing getHelp option, based on open/closedness
let iconClass = 'fa fa-angle-';
isGetHelpClosed ? iconClass += 'down' : iconClass += 'up';

renderIsCompleteMessage() {
const initiatingTrigger = this.props.shortcut.getDisplayText();
return (
<ul className="context-get-help" ref="contextGetHelp">
<span>
<li
className="context-get-help-li"
data-active={this.state.selectedIndex === 0}
onMouseEnter={() => { this.setSelectedIndex(0); }}
onClick={() => { this.setSelectedIndex(-1); }}
>
<span className="context-get-help-text">
<i>{initiatingTrigger} is already complete</i>
get help with {initiatingTrigger}
<span className={iconClass}></span>
</span>
</li>
</ul>

<span className="context-get-help-options">
{!isGetHelpClosed && this.state.getHelpOptions.map((option, index) => {
// the parent 'get help' option is not included in the getHelpOptions array
// but it is included as a selectedIndex, so there is an off by one that needs
// to be calculated, hence the updatedIndex + 1 from the index of the getHelpOptions
const updatedIndex = index + 1;
return (
<li key={updatedIndex}
data-active={this.state.selectedIndex === updatedIndex}
onClick={option.onSelect}
onMouseEnter={() => { this.setSelectedIndex(updatedIndex); }}
>
{option.text}
</li>
);
})}
</span>
{(!isGetHelpClosed && isInformationAvailable) && this.renderHorizontalLine()}
{(!isGetHelpClosed && isMissingParent) && this.renderIsMissingParent()}
</span>
);
}

renderIsMissingParent() {
renderIsCompleteMessage() {
const initiatingTrigger = this.props.shortcut.getDisplayText();
const infoIconiconClass = "fa fa-info-circle";
return (
<ul className="context-get-help" ref="contextGetHelp">
<li
className="context-get-help-li"
>
<span className="context-get-help-text">
<i>{initiatingTrigger} is missing a parent</i>
</span>
</li>
</ul>
<li className="context-get-help-li" >
<span className="context-information-text">
<span className={infoIconiconClass}></span>
<i>{initiatingTrigger} is already complete</i>
</span>
</li>
);
}

renderIsMissingParent() {
const shortcut = this.props.shortcut;
const initiatingTrigger = shortcut.getDisplayText();
const potentialParentText = shortcut.potentialParents.map(parentID => this.props.shortcutManager.getShortcutLabel(parentID)).join(", or");
const infoIconiconClass = "fa fa-info-circle";
return (
<li className="context-get-help-li">
<span className="context-information-text">
<span className={infoIconiconClass}></span>
<i>{initiatingTrigger} needs more context, try mentioning {potentialParentText} beforehand</i>
</span>
</li>
);
}

renderHorizontalLine() {
return (
<hr/>
);
}

render() {
// If the shortcut we're responsible for is missing a parent, display a message to the user to avoid confusion
if (!this.props.shortcut.hasParentContext() && this.props.shortcut.hasChildren()) return this.renderIsMissingParent();
// If the shortcut we're responsible for is complete, display a message to the user to avoid confusion
if (this.props.shortcut.isComplete) return this.renderIsCompleteMessage();
// Else we should display all our getHelp message
const initiatingTrigger = this.props.shortcut.getDisplayText();
let iconClass = 'fa fa-angle-';
this.state.selectedIndex === -1 ? iconClass += 'down' : iconClass += 'up';
// Decide the list content and render whatever it is in the UL element
let listContent = null;
if (this.props.shortcut.isMissingParent && this.props.shortcut.hasChildren()) {
// If the shortcut we're responsible for is missing a parent but is already expanded, display a message to the user to avoid confusion
listContent = this.renderIsMissingParent();
} else if (this.props.shortcut.isComplete) {
// Else, if the shortcut we're responsible for is complete, display a message to the user to avoid confusion
listContent = this.renderIsCompleteMessage();
} else {
// Else we should display all our getHelp message
listContent = this.renderOptionsWithGetHelp();
}

return (
<ul className="context-get-help" ref="contextGetHelp">
<li
className="context-get-help-li"
data-active={this.state.selectedIndex === 0}
onMouseEnter={() => { this.setSelectedIndex(0); }}
>
<span className="context-get-help-text">
<i>get help with {initiatingTrigger}</i>
<span className={iconClass}></span>
</span>
</li>
{this.renderOptions()}
{listContent}
</ul>
);
}
Expand Down
12 changes: 7 additions & 5 deletions src/context/ContextGetHelp.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ ul.context-get-help {
margin-bottom: 5px;

.context-get-help-text {
span {
span.fa-angle-up, span.fa-angle-down {
padding: 0;
padding-left: 15px;
}
}
.context-information-text {
span.fa-info-circle {
padding: 0 5px 0 0;
}
}
}

.context-get-help-options {
li:first-child {
border-top: 1px solid $line-gray;
}

li:last-child {
margin-bottom: 5px;
}
Expand Down
1 change: 1 addition & 0 deletions src/notes/FluxNotesEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1787,6 +1787,7 @@ class FluxNotesEditor extends React.Component {
onSelected={this.onCompletionComponentValueSelection}
closePortal={this.closeCompletionPortal}
shortcut={this.state.completionComponentShortcut}
shortcutManager={this.props.shortcutManager}
state={this.state.state}
insertShortcut={this.insertShortcut}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/notes/SuggestionPortalPlaceholderSearchIndex.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class SuggestionPortalPlaceholderSearchIndex extends SuggestionPortalSearchIndex
const relevantShortcuts = [];

placeholders.forEach((placeholder) => {
const triggers = this.shortcutManager.getTriggersForShortcut(placeholder.id);
const triggers = this.shortcutManager.getTriggersWithoutLabelForShortcut(placeholder.id);
triggers.forEach((trigger) => {
const triggerNoPrefix = trigger.name.substring(1);
relevantShortcuts.push({
Expand Down
2 changes: 1 addition & 1 deletion src/notes/SuggestionPortalShortcutSearchIndex.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class SuggestionPortalShortcutSearchIndex extends SuggestionPortalSearchIndex {
allShortcutObjs.forEach((shortcutObj) => {
const shortcutId = shortcutObj.id;
const shortcutMetadata = this.shortcutManager.getShortcutMetadata(shortcutId);
const triggers = this.shortcutManager.getTriggersForShortcut(shortcutId);
const triggers = this.shortcutManager.getTriggersWithoutLabelForShortcut(shortcutId);
// Scores get sorted from smallest to greatest
// ActiveContexts is sorted from most recent to least recent
// We want shortcuts for the most recent shortcuts to have the smallest bonus score, so as to appear earlier
Expand Down
18 changes: 18 additions & 0 deletions src/shortcuts/CreatorBase.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import EntryShortcut from './EntryShortcut';
import _ from 'lodash';

export default class CreatorBase extends EntryShortcut {
constructor(onUpdate, metadata, patient, shortcutData) {
Expand All @@ -22,4 +23,21 @@ export default class CreatorBase extends EntryShortcut {
get isComplete() {
return this.hasParentContext() && this.hasChildren();
}

get isMissingParent() {
return !this.hasParentContext();
}

get potentialParents() {
const knownParent = this.metadata["knownParentContexts"];
if (knownParent === 'Patient' || knownParent === undefined) return [];
if (_.isArray(knownParent)) {
return knownParent;
} else if (_.isString(knownParent)) {
return [knownParent];
} else {
console.warn("unknown type for knownParent: element looks like ", knownParent);
return [];
}
}
}
24 changes: 20 additions & 4 deletions src/shortcuts/CreatorIntermediary.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Shortcut from './Shortcut';
import Lang from 'lodash';
import _ from 'lodash';

export default class CreatorIntermediary extends Shortcut {
constructor(onUpdate, metadata) {
Expand All @@ -15,11 +15,11 @@ export default class CreatorIntermediary extends Shortcut {
initialize(contextManager, trigger = undefined, updatePatient = true) {
super.initialize(contextManager, trigger, updatePatient);

if (Lang.isUndefined(this.parentContext)) {
if (_.isUndefined(this.parentContext)) {
super.determineParentContext(contextManager, this.metadata["knownParentContexts"], this.metadata["parentAttribute"]);
}

if (!Lang.isUndefined(this.parentContext) && this.parentContext.children.indexOf(this) === -1) {
if (!_.isUndefined(this.parentContext) && this.parentContext.children.indexOf(this) === -1) {
this.parentContext.setAttributeValue(this.metadata["parentAttribute"], true, false, updatePatient);
this.parentContext.addChild(this);
}
Expand Down Expand Up @@ -115,10 +115,26 @@ export default class CreatorIntermediary extends Shortcut {
}

hasValueObjectAttributes() {
return !Lang.isEmpty(this.metadata["valueObjectAttributes"]);
return !_.isEmpty(this.metadata["valueObjectAttributes"]);
}

get isComplete() {
return this.hasParentContext() && this.hasChildren();
}

get isMissingParent() {
return !this.hasParentContext();
}
get potentialParents() {
const knownParent = this.metadata["knownParentContexts"];
if (knownParent === 'Patient' || knownParent === undefined) return [];
if (_.isArray(knownParent)) {
return knownParent;
} else if (_.isString(knownParent)) {
return [knownParent];
} else {
console.warn("unknown type for knownParent: element looks like ", knownParent);
return [];
}
}
}
25 changes: 20 additions & 5 deletions src/shortcuts/ShortcutManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ class ShortcutManager {
} else {
addTriggerForCurrentShortcut.bind(this)(triggers, item);
}
// If this shortcut has a label, then add the label as a shortcut.
if (item.label) {
// Add a string trigger for incomplete placeholder
addTriggerForCurrentShortcut.bind(this)({
Expand Down Expand Up @@ -341,7 +342,7 @@ class ShortcutManager {
}
let numberOfValidTriggers;
if (this.triggersPerShortcut[shortcutId]) {
// If the shortcut has a label defined, don't include in in the list of valid triggers per shortcut
// If the shortcut has a label defined, don't include in in the list of valid triggers per shortcut
numberOfValidTriggers = this.triggersPerShortcut[shortcutId].length - (shortcut.label ? 1 : 0);
} else {
numberOfValidTriggers = shortcut.label ? 1 : 0;
Expand Down Expand Up @@ -402,10 +403,20 @@ class ShortcutManager {
// stringTriggers is directly from shortcut metadata
const stringTriggers = this.shortcuts[shortcutId].stringTriggers;
// triggers is the computed options based on the valueset defined in metadata
const triggers = this.getTriggersForShortcut(shortcutId, context);

// Filter out the label from triggers if there are defined stringTriggers that can be added
return triggers.filter(t => stringTriggers.length === 0 || t.name !== label);
// Clone this information so we aren't changing anything by reference
const triggers = [...this.getTriggersForShortcut(shortcutId, context)];

// Get the index of the label in the triggers list
const indexOfLabel = _.findIndex(triggers, t => t.name === label);
// If there is an instance of the label, and the stringTriggers isn't empty then we want to remove this instance of the label
// When stringTriggers doesn't have a length, we might be using the label as a shorthand for writing down the string trigger as well
if (indexOfLabel !== -1 && stringTriggers.length !== 0) {
// We only splice out this one instance of it in case the string trigger itself happens to match the label
// In this case there will be two instances of the label in our list and we want to remove one of them
// In the case where there is just one instance, then we can remove it safely
triggers.splice(indexOfLabel, 1);
}
return triggers;
}

getKeywordsForShortcut(shortcutId, context) {
Expand Down Expand Up @@ -439,6 +450,10 @@ class ShortcutManager {
return this.shortcuts[shortcutId]["shortcutGroupName"];
}

getShortcutLabel(shortcutId) {
return this.shortcuts[shortcutId].label;
}

getShortcutMetadata(shortcutId) {
return this.shortcuts[shortcutId];
}
Expand Down
5 changes: 5 additions & 0 deletions src/shortcuts/Shortcuts.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
{ "type": "CreatorBase",
"id": "StagingCreator",
"label": "#staging",
"name": "staging",
"subtype": "menu",
"getData": null,
Expand Down Expand Up @@ -113,6 +114,7 @@
},
{ "type": "CreatorBase",
"id": "ProgressionCreator",
"label": "#disease status",
"name": "disease status",
"subtype": "menu",
"getData": null,
Expand Down Expand Up @@ -193,6 +195,7 @@
},
{ "type": "CreatorBase",
"id": "ToxicityCreator",
"label": "#toxicity",
"name": "toxicity",
"subtype": "menu",
"getData": null,
Expand Down Expand Up @@ -354,6 +357,7 @@
{ "type": "InsertValue",
"subtype": "choice",
"id": "ConditionInserter",
"label": "@condition",
"getData": {"object": "patient", "method": "getConditions", "itemKey": "entryInfo.entryId.id", "itemContext":"type", "dateLabel": "diagnosisDate"},
"isContext": true,
"isGlobalContext": true,
Expand Down Expand Up @@ -562,6 +566,7 @@
},
{ "type": "CreatorBase",
"id": "MedicationChangeReduceCreator",
"label": "#reduce medication",
"name": "reduce medication",
"subtype": "menu",
"getData": null,
Expand Down

0 comments on commit 14ec01c

Please sign in to comment.