Skip to content

Implementing Search Indexing for a New Visualizer Type

Matthew Gramigna edited this page Mar 14, 2019 · 1 revision

When adding a new visualizer to the targeted data panel, the data of that visualizer must be indexed to be searchable. There are many example indexers to go off, but there are still a few important steps to follow in order to get the data to be searchable.

Current Visualizers/Indexers

Visualizer type => Indexer

  • NameValuePairsm => NameValuePairsIndexer
  • NarrativeOnly => NameValuePairsIndexer
  • Columns => ColumnsIndexer
  • Events => EventsIndeser
  • Medications => MedicationsIndexer
  • ValueOverTime => ValueOverTimeIndeser
  • ReviewOfSystemsValues => ReviewOfSystemsValuesIndexer
  • ClusterPoints => ClusterPointsIndexer

Writing a New Indexer

This assumes you have already written a new visualizer for the targeted data panel, and that this visualizer has been added to the visualizers array in VisualizerManager.jsx as follow:

visualizers = [
    /* other visualizers */
    { "dataType": "NewDataType", "visualizerType": "...", "visualizer": NewVisualizer }
];

Create a New Indexer

Create a new file src/patientControl/NewIndexer.js that extends BaseIndexer:

import BaseIndexer from './BaseIndexer';

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

export default NewIndexer;

The call to super.indexData will index the section and subsection name.

Add Indexer to VisualizerManager

There is a function in VisualizerManager.jsx that returns the corresponding indexer to use based on the given dataType. Add your indexer to the switch statement so its indexData method gets called:

/* src/summary/VisualizerManager.jsx */

getIndexer(dataType) {
    switch(dataType) {
        case "NameValuePairs":
        case "NarrativeOnly":
            return new NameValuePairsIndexer();
        case "Columns":
            return new ColumnsIndexer();
        case "Events":
            return new EventsIndexer();
        case "Medications":
            return new MedicationsIndexer();
        case "ValueOverTime":
            return new ValueOverTimeIndexer();
        case "DiseaseStatusValues":
            return new DiseaseStatusValuesIndexer();
        case "ReviewOfSystemsValues":
            return new ReviewOfSystemsValuesIndexer();
        case "ClusterPoints":
            return new ClusterPointsIndexer();
        case "TreatmentOptions":
            return new BaseIndexer();
        case "NewDataType": // Adding case for new indexer
            return new NewIndexer();
        default:
            console.warn(`Targeted Data Panel data type '${dataType}' has no registered indexer.`);
            return null;
    }
}

Implementing the indexData Function

Intro

Now that the indexer will be properly called to index the data of the new visualizer, you need to actually tell the search indexer to store relevant data from the visualizer in order for it to be searchable.

indexData takes the following arguments:

  • section: The name of the section for the data
  • subsection: The name of the relevant subsection (empty string if no subsection)
  • data: The data that gets rendered in the visualizer
  • searchIndex: A reference to the class in SearchIndex.js. Used to make a call to actually index the data
  • onHighlight: Handler function for what happens when hovering over the search suggestion. For visualizers, this scrolls the data into view in the targeted data panel

For a given document (object that gets indexed), there are two fields that are searchable aside from the section name and subsection name (these are indexed by the base indexer): valueTitle and value. The valueTitle is the "key" that describes the value, e.g. for the data element "date" whose value is "1/1/2019", the valueTitle would be "date" and the value would be "1/1/2019"

An example of such a document would be the following:

{
    id: "some_unique_id",
    section: "Section Name",
    subsection: "Subsection Name",
    valueTitle: "Date",
    value: "1/1/2019",
    onHighlight: () => console.log('hello');
}

When this document gets indexed, the following search queries (or substrings of these queries) will result in this document being returned as a match:

  • "Section Name" => matches on section field
  • "Subsection Name" => matches on subsection field
  • "Date" => matches on valueTitle field
  • "2019" => matches on value field

Implementation

At a high level, implementation is done by calling searchIndex.addSearchableData and passing in an object of the above form. The way our sections work in Flux Notes, the data argument will either by an array or an object.

Iterate through what you need from the data object, and make the above call with an object of the form of the example. There are many examples of this done in src/patientConrol. Here is a simple example of how it could work:

import BaseIndexer from './BaseIndexer';

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

        const sectionId = super.getStringForId(section); // removes spaces and illegal characters and adds underscores

        data.forEach(item => {
            searchIndex.addSearchableData({
                id: `${sectionId}_${item.foo}`, // uniquely identified by section name and valueTitle
                section,
                subsection,
                valueTitle: item.foo,
                value: item.bar,
                onHighlight
            });
        });
    }
}

export default NewIndexer;
Clone this wiki locally