npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

elastic-composer

v4.8.1

Published

A high-level Elasticsearch query manager and executor. Filter fields, find search suggestions, and paginate query results for your indicies. Comes with addons for persisting and rehydrationg filter state from localStorage and the URL. Batteries included f

Downloads

4

Readme

elastic-composer

A high-level Elasticsearch query manager and executor. Filter fields, find search suggestions, and paginate query results for your indicies. Comes with addons for persisting and rehydrationg filter state from localStorage and the URL. Batteries included for optionally initializing via index introspection. Fully configurable. Very delightful. Try a slice 🍰!

Example:

const client = new AxiosESClient('my_url/my_index');
const crm = new Manager(client);

// set filters on the elasticsearch index fields 'age', 'isMarried', 'id', 'tags', and 'user_profile.location'
crm.filter.range.setFilter('age', {greaterThan: 20, lessThanEqual: 60})
crm.filter.boolean.setFilter('isMarried', {state: true})
crm.filter.exists.setFilter('id')
crm.filter.multiselect.setFilter('tags', { isHuman: { inclusion: 'include' }, hasBlueHair: { inclusion: 'exclude' }})
crm.filters.geo.addToFilter('user_profile.location', 'my_location_search', {
    inclusion: 'include',
    kind: 'should',
    points : [
        {"lat" : 40, "lon" : -70},
        {"lat" : 30, "lon" : -80},
        {"lat" : 20, "lon" : -90}
    ]
})

autorun(() => {
  console.log(crm.results) // results of the above compound query
})

Note: Internally all filter changes create sideEffectRequests that are put onto a processing queue. The above example will create 5 requests - one for each setFilter | addFilter action. However, because there is built in debouncing, if these actions occur within the debounce window, only the last request (fully compounded with all filters applied) will be executed.

Example with React:

export default observer(() => {
    const crm = useContext(Context.crm);
    return (
      <div>
        <div onClick={() => crm.filter.exists.setFilter('id')}/>
        <div onClick={() => crm.filter.exists.clearFilter('id')}/>
        <div>
          {crm.results}
        </div>
      </div>
    )
})

Install

npm install --save elastic-composer

Peer dependencies

This package requires that you also install:

{
        "await-timeout": "^1.1.1",
        "axios": "^0.19.1", <------- only needed if using the AxiosESClient
        "lodash.chunk": "^4.2.0",
        "mobx": "^5.14.2"
        "lodash.debounce": "^4.0.8", <------- only needed if using the History API
        "query-string": "^6.11.1", <------- only needed if using the History API
        "query-params-data": "^0.1.1", <------- only needed if using the History API
}

About

This library is a high level aid in querying an Elasticsearch index and building applications ontop of indexes. It is designed to bind directly to the view layer (React, Vue, Vanilla, etc..) of your app, and it handles the vast majority of associated business logic internally.

This library is written in MobX, which makes it reactive. If you don't want to use MobX, you can convert any attribute (see all attributes in the API) to an observable stream (RxJS, 😎) using the mobx-utils tool.

Paradigm

TL;DR: You describe how you want to filer each field via a Filter API customized to your index and the manager handles querying, state, and pagination.

The general paradigm is as follows:

There are 4 API's:

  • Filter API
  • Suggestion API
  • History API
  • Manager API

The flow is:

  1. You define all the fields of an ES index that you want to use via (A) configuration objects in either the Filter or Suggestion API or (B) introspection abilities in the Manager API.
  2. Once you have fields set that you can filter or find suggestions on, you use the Filter API to filter results and the Suggestion API to get suggestions (for parameters to use in filters - such as fuzzy or prefix search of values).
  3. The Manager API gives you access to results and allows you to paginate over the results.
  4. The History API records Filters and Suggestions that have been set, persists this state to the URL in a persistent store (like localStorage), and rehydrates from persisted state.

Everything in this library is reactive. So once you set a filter, the manager will react to the change, and submit a new query to Elasticsearch using all the filters that have been set across all the fields. The manager handles debouncing, throttling and batching queries.

In addition to simply running new queries, Filters and Suggestions provide opinionated aggregates that help inform how well the filter is doing. For example, the Range Filter gives you aggregates that show you a histogram of documents with the filter applied and without it applied. By default, aggregates are turned off by default. The paradigm with aggregates is to turn them on when a user is accessing a UI element that allows seeing aggregate data and turn them off when the UI element is no long visible. Because aggregates will respond to all filter changes, if you don't turn them off when not in use, you will submit meaningless queries to Elasticsearch.

Available filters and suggestions

The currently available Filters are:

  • range: Filter documents by fields that fit within a LT (<), LTE(<=), GT(>), GTE(>=) range
  • boolean: Filter documents by fields that have a value of either true or false
  • exists: Filter documents by fields that have any value existing for that field
  • multiselect: Filter documents that have fields matching certain values (includes or excludes)
  • geo: Filter documents that have fields with geo_point data in them

The currently available suggestions are:

  • prefix: Get suggestions for fields based on matches with the same prefix
  • fuzzy: Get suggestions for fields based on fuzzy matching

Enabling filters and suggestions

All Filters affect both the query and aggs part of an Elasticsearch request object. The query part is how the Filter impacts which documents match the filters. The aggs part provides information about how successful the filter is - showing things like histogram of range results, count of exists and not exists, etc... By default, the aggs part is disabled for every Filter. You should use setAggsEnabledToTrue and setAggsEnabledToFalse to toggle aggs for a Filter. The idea is to only run aggs queries when you want to show this data to the user.

Similarly, Suggestions are disabled by default. For the same reason above, suggestions shouldn't run unless you explicitly are showing suggestion data to a user. To toggle Suggestion enabled state use the methods setEnabledToTrue and setEnabledToFalse.

How filters and suggestions affect one another

The interplay between Suggestions and Filters is such:

  • Suggestions don't affect Filters, but they will react to every Filter change
  • Filters affect Suggestions and don't react to Suggestion changes

Extending and customizing filters

Extending and overriding the set of usable Filters or Suggestions is both possible, and easy. See Extending Filters and Suggestions for a complete guide. The basic idea is that you extend a base Filter or base Suggestion and fill out methods that tell: (A) when the manager should react to changes, (B) how to mutate a Elasticsearch request object to add Filter or Suggestion specific query and aggs, (C) how to parse an Elasticsearch response object to extract aggs.

Quick Examples

Various use cases are described below. Be sure to check out the API for the full range of attributes and methods available on the Manager, Filters, and Suggestions.

Instantiate a manager

import {AxiosESClient, Manager} from 'elastic-composer';

// instantiate an elasticsearch axios client made for this lib
const client = new AxiosESClient('my_url/my_index');

// instantiate a manager
const manager = new Manager(client, {
    pageSize: 100,
    queryThrottleInMS: 350,
    fieldBlackList: ['id']
});

Instantiate a manager with specific config options for a range filter

import {AxiosESClient, Manager, RangeFilter} from 'elastic-composer';

// set the default config all filters will have if not explicitly set
// by default we don't want aggs enabled unless we know the filter is being shown in the UI. So,
// we use lifecycle methods in react to toggle this config attribute and set the default to `false`.
const defaultRangeFilterConfig = {
    aggsEnabled: false,
    defaultFilterKind: 'should',
    getDistribution: true,
    getRangeBounds: true,
    rangeInterval: 1
};

// explicitly set the config for certain fields
const customRangeFilterConfig = {
    age: {
        field: 'user.age',
        rangeInterval: 10
    },
    invites: {
        field: 'user.invites',
        getDistribution: false
    }
};

// instantiate a range filter
const rangeFilter = new RangeFilter(defaultRangeFilterConfig, customRangeFilterConfig);

const options = {
    pageSize: 100,
    queryThrottleInMS: 350,
    fieldBlackList: ['id'],
    filters: {range: rangeFilter}
};

const manager = new Manager(client, options);

Setting the fieldNameModifier for all fields in a filter

The fieldNameModifier can be used to modify what the field name sent to Elasticsearch looks like. This is useful if you want to take a field name such as tags and turn it into tags.keyword for matching purposes.

The modifier is a function with the signature (fieldName: string) => string

import {AxiosESClient, Manager, MultiSelectFilter} from 'elastic-composer';

// set the default config all filters will have if not explicitly set
// by default we don't want aggs enabled unless we know the filter is being shown in the UI. So,
// we use lifecycle methods in react to toggle this config attribute and set the default to `false`.

const defaultMultiSelectFilterConfig = {
    defaultFilterKind: 'should',
    defaultFilterInclusion: 'include',
    defaultMatch: 'match',
    getCount: true,
    aggsEnabled: false,
    fieldNameModifierQuery: (fieldName: string) => `${fieldName}`,
    fieldNameModifierAggs: (fieldName: string) => `${fieldName}.keyword`
};


// instantiate a range filter
const multiselectFilter = new MultiSelectFilter(defaultMultiSelectFilterConfig);

const options = {
    pageSize: 100,
    queryThrottleInMS: 350,
    fieldBlackList: ['id'],
    filters: {multiselect: multiselectFilter}
};

const manager = new Manager(client, options);

Add a custom filter during manager instantiation

import MyCustomFilter from 'my_custom_filter';
import {AxiosESClient, Manager} from 'elastic-composer';

const client = new AxiosESClient('my_url/my_index');
const newCustomFilter = new MyCustomFilter();

const manager = new Manager(client, {
    pageSize: 100,
    queryThrottleInMS: 350,
    fieldBlackList: ['id'],
    filters: {myNewFilterName: newCustomFilter}
});

Add a custom suggestion during manager instantiation

import MyCustomSuggestion from 'my_custom_suggestion';
import {AxiosESClient, Manager} from 'elastic-composer';

const client = new AxiosESClient('my_url/my_index');
const newCustomSuggestion = new MyCustomSuggestion();

const manager = new Manager(client, {
    pageSize: 100,
    queryThrottleInMS: 350,
    fieldBlackList: ['id'],
    suggestions: {myNewSuggestionName: newCustomSuggestion}
});

Adding a custom client to the manager

If you don't have permissions set up on your Elasticsearch cluster, you will most likely want to create a custom client that uses your backend as a pass through layer for making Elasticsearch calls.

An example could look like this:

import {Manager, IClient, ESRequest, ESResponse, ESMappingType} from 'elastic-composer';

/**
 * Create a custom client that works on a specific through backend graphql nodes
 * In this case, the client uses the nodes 'creatorCRMSearch' and 'creatorCRMFields'
 */
class CreatorIndexGQLClient<Source extends object = object> implements IClient {
    public graphqlClient: GqlClient;

    constructor(graphqlClient: GqlClient) {
        if (graphqlClient === undefined) {
            throw new Error(
                'GraphqlQL client is undefined. Please instantiate this class with a GqlClient instance'
            );
        }
        this.graphqlClient = graphqlClient;
    }

    public search = async (search: ESRequest): Promise<ESResponse<Source>> => {
        const {data} = await this.graphqlClient.client.query({
            query: gql`
                query CreatorCRMSearch($search: JSON) {
                    creatorCRMSearch(search: $search)
                }
            `,
            fetchPolicy: 'no-cache',
            variables: {search: JSON.stringify(search)}
        });
        return JSON.parse(data.creatorCRMSearch);
    };

    public mapping = async (): Promise<Record<string, ESMappingType>> => {
        const {data} = (await this.graphqlClient.client.query({
            query: gql`
                query CreatorCRMFields {
                    creatorCRMFields
                }
            `,
            fetchPolicy: 'no-cache'
        })) as any;
        return JSON.parse(data.creatorCRMFields);
    };
}

const customClient = new CreatorIndexGQLClient(gqlClient);
const creatorCRM = new Manager(customClient);

Set middleware

import {Middleware} from 'elastic-composer';

const logRequestObj: Middleware = (
    _effectRequest: EffectRequest<EffectKinds>,
    request: ESRequest
) => {
    console.log(request);
    return request;
};

manager.setMiddleware([logRequestObj]);

Get the initial results for a manager

All queries are treated as requests and added to an internal queue. Thus, you don't await this method but, react to the manager.results attribute.

manager.runStartQuery();

Run a custom elasticsearch query using the current filters

If you wanted to bulk export a subset of the filtered results without having to paginate programmatically, you could request the results for a much larger page size this way over a reduced field list:

const results = await manager.runCustomFilterQuery({whiteList: ['id'], pageSize: 10000});

Get the raw elasticsearch query derived from the current filters

const rawEsQuery = await manager.getCurrentEsQuery();

Setting a range filter

manager.filters.range.setFilter('age', {greaterThanEqual: 20, lessThan: 40});

Note: This triggers a query to rerun with all the existing filters plus the range filter for age will be updated to only include people between the ages of 20-40 (inclusive to exclusive).

Setting a boolean filter

manager.filters.boolean.setFilter('isActive', {state: true});

Setting a exists filter

For example, this will filter all documents so only the ones with a facebook.id are shown

manager.filters.boolean.setFilter('facebook.id', {exists: true});

Setting a multi-select filter

A multi select filter can be set in two ways: (1) all selections at once, or (2) one selection at a time.

To set all selections at once, you would do something like:

manager.filters.multiselect.setFilter('tags', {
    is_good_user: {inclusion: 'include', match: 'match'},
    has_green_hair: {inclusion: 'exclude', kind: 'must'},
    likes_ham: {inclusion: 'include', kind: 'should', match: 'match_phrase'}
});

Notice how kind is optional. If its not specified, it will default to whatever defaultFilterKind is set to for the filter (aka manager.filter.multiselect.fieldConfigs['tags].defaultFilterKind)

Notice how match is optional. If its not specified, it will default to whatever defaultMatch is set to for the filter (aka manager.filter.multiselect.fieldConfigs['tags].defaultMatch)

To set one selection at a time, you would do:

manager.filters.multiselect.addToFilter('tags', 'has_green_hair', {
    inclusion: 'exclude',
    kind: 'must',
    match: 'match_phrase'
});

Setting a geo filter

Geo filters implement geo bounding box, geo distance, and geo polygon queries.

Like a multiselect filter, you can add all filters at once for a field using setFilter or add them one by one using addFilter.

crm.filters.geo.addToFilter('user_profile.location', 'my_first_loc', {
    'kind': 'should',
    'inclusion': 'exclude',
    'distance': '100mi',
    'lat': 34.7850143,
    'lon': -92.3912103
})

crm.filters.geo.addToFilter('user_profile.location', 'my_second_loc', {
    'kind': 'must',
    'inclusion': 'include',
    "top_left" : {
        "lat" : 40.73,
        "lon" : -74.1
    },
    "bottom_right" : {
        "lat" : 40.01,
        "lon" : -71.12
    }
})

crm.filters.geo.addToFilter('user_profile.location', 'my_third_loc', {
    "points" : [
        {"lat" : 40, "lon" : -70},
        {"lat" : 30, "lon" : -80},
        {"lat" : 20, "lon" : -90}
    ]
})

Clearing a single selection from a multi-select filter

manager.filters.multiselect.removeFromFilter('tags', 'has_green_hair');

Clearing a filter

For example, to clear the isActive field on a boolean filter, we would do:

manager.filters.boolean.clearFilter('tags');

Setting a prefix suggestion

manager.suggestions.prefix.setSearch('tags', 'blu');

Setting a fuzzy suggestion

manager.suggestions.fuzzy.setSearch('tags', 'ca');

Access suggestion results

All suggestions have the same interface (currently). For both prefix and fuzzy would get the suggestions for a search like:

manager.suggestions.fuzzy.fieldSuggestions['tags'];

// => [{ suggestion: 'car', count: 120}, { suggestion: 'can', count: 9 }]

Access the results of a query

manager.results; // Array<ESHit>

Results are an array where each object in the array has the type:

type ESHit<Source extends object = object> = {
    _index: string;
    _type: string;
    _id: string;
    _score: number;
    _source: Source;
    sort: ESRequestSortField;
};

_source will be the document result from the index.

Thus, you would likely use the results like:

manager.results.map(r => r._source);

Access the raw response object of the current query

manager.rawESResponse

// => 
// {"took":1,"timed_out":false,"_shards":{"total":5,"successful":5,"skipped":0,"failed":0},"hits":{"total":2178389,"max_score":0.0,"hits":[]}}
//

Paginating through the results set

manager.nextPage();
manager.prevPage();

manager.currentPage; // number
// # => 0 when no results exist
// # => 1 for the first page of results

Checking if there is another page to paginate to

manager.hasNextPage

Enabling aggregation data for a filter

By default, aggregation data is turned off for all filter. This data shows things like count of exists field, histogram of range data, etc..

manager.filters.boolean.setAggsEnabledToTrue('tags');

The idea with enabling and disabling aggregation data is that these aggregations only need to run when a filter is visible to the user in the UI. Thus, enabling and disabling should mirror filter visibility in the UI.

Disabling aggregation data for a filter

manager.filters.boolean.setAggsEnabledToFalse('tags');

Enabling suggestions

Similar to filters, suggestions are disabled by default because they rely on elasticsearch aggregations to run, and there is no point in collecting the data unless the user cares about it.

manager.suggestions.fuzzy.setEnabledToTrue('tags');

Disabling suggestions

manager.suggestions.fuzzy.setEnabledToFalse('tags');

Setting filter 'should' or 'must' kind

All filters can be use in should or must mode. By default, all filters are should filters unless explicitly changed to must filters. Read this for more info on the difference between should and must

manager.filters.boolean.setKind('facebook.id', 'must');

// or to go back to should:

manager.filters.boolean.setKind('facebook.id', 'should');

Clearing all filters

manager.clearAllFilters()

Clearing all suggestions

manager.clearAllSuggestions()

Looking at all active suggestions

manager.activeSuggestions

// => 
// { tags: [PrefixSuggestion, FuzzySuggestion], location: [PrefixSuggestion]}

Looking at all active filters

manager.activeFilters

// => 
// { tags: [MultiSelectFilter, ExistsFilter], location: [RangeFilter, ExistsFilter]}

Looking at all the Filter and Suggestion instances available for a filed

manager.fieldsWithFiltersAndSuggestions

// =>
// { tags: { filters: [MultiSelectFilter, ExistsFilter] suggestions: [PrefixSuggestion, FuzzySuggestion]} }

Using the history API

const userHistory = new History(manager, 'user', { // set the url's query param key to `user`
    historyPersister: localStorageHistoryPersister('user'), // set the local storage suffix key to `user`
    historySize: 4
});

API

Manager

Initialization

The manager constructor has the signature (client, options) => ManagerInstance

Client

client is an object than handles submitting query responses. It has the signature:

interface IClient<Source extends object = object> {
    search: (request: ESRequest) => Promise<ESResponse<Source>>;
    mapping: () => Promise<Record<string, ESMappingType>>;
    // With ESMappingType equal to https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
}

At the moment there only exists an AxiosESClient client. This can be imported via a named import:

import {AxiosESClient} from 'elastic-composer';

const axiosESClient = new AxiosESClient(endpoint);

// endpoint is in the form: blah2lalkdjhgak.us-east-1.es.amazonaws.com/myindex1
Options

options are used to configure the manager. There currently exist these options:

type ManagerOptions = {
    pageSize?: number;
    queryThrottleInMS?: number;
    fieldWhiteList?: string[];
    fieldBlackList?: string[];
    middleware?: Middleware[];
    filters?: IFilters;
    suggestions?: ISuggestions;
};
  • pageSize: the number of results to expect when calling manager.results. The default size is 10.
  • queryThrottleInMS: the amount of time to wait before executing an Elasticsearch query. The default time is 1000.
  • fieldWhiteList: A list of elasticsearch fields that you only want to allow filtering on. This can't be used with fieldBlackList. Only white list fields will be returned in an elasticsearch query response.
  • fieldBlackList: A list of elasticsearch fields that you don't want to allow filtering on. This can't be used with fieldWhiteList. Black list fields will be excluded from an elasticsearch query response.
  • middleware: An array of custom middleware to run during elasticsearch request object construction. See below for the type.
  • filters: An object of filter instances. Default filters will be instantiate if none are specified in this options field. This options field however can be used to override existing filters or specify a custom one.
  • suggestions: An object of suggestion instances. Default suggestions will be instantiated if none are specified in this options field. This options field however can be used to override existing suggestions or specify a custom one.

The middleware function type signature is:

Middleware = (effectRequest: EffectRequest<EffectKinds>, request: ESRequest) => ESRequest;

Example of overriding the range filter:

const options = {filters: {range: rangeFilterInstance}};

Example of overriding the fuzzy suggestion:

const options = {suggestions: {fuzzy: fuzzyFilterInstance}};

Methods

| method | description | type | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | nextPage | paginates forward | (): void | | prevPage | paginates backward | (): void | | clearAllFilters | clears all active filters | (): void | | clearAllSuggestions | clears all active suggestions | (): void | | getFieldNamesAndTypes | runs an introspection query on the index mapping and generates an object of elasticsearch fields and the filter type they correspond to | async (): void | | runStartQuery | runs the initial elasticsearch query that fetches unfiltered data | (): void | | runCustomFilterQuery | runs a custom query using the existing applied filters outside the side effect queue flow. white lists and black lists control which data is returned in the elasticsearch response source object | async (options?: {fieldBlackList?: string[], fieldWhiteList?: string[], pageSize?: number }): Promise<ESResponse> | | setMiddleware | adds middleware to run during construction of the elasticsearch query request object | (middlewares: Middleware): void. Middleware has the type (effectRequest: EffectRequest<EffectKinds>, request: ESRequest) => ESRequest | | getCurrentEsQuery | gets the raw Elasicsearch query that is derived from the current state | () => ESRequest |

Attributes

| attribute | description | notes | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | isSideEffectRunning | a flag telling if a query is running | boolean | | currentPage | the page number | 0 if there are no results. 1 for the first page. etc... | | fieldWhiteList | the white list of fields that filters can exist for | | | fieldBlackList | the black list of fields that filters can not exist for | | | pageSize | the page size | The default size is 10. This can be changed by setting manager options during init. | | queryThrottleInMS | the throttle time for queries | The default is 1000 ms. This can be changed by setting manager options during init. | | filters | the filter instances that the manager controls | | indexFieldNamesAndTypes | A list of fields that can be filtered over and the filter name that this field uses. This is populated by the method getFieldNamesAndTypes. | | results | the results of the most recent query | The results type is Array. See the results quick example doc for the type | | rawESResponse | The response object from the client from the query | ESResponse | | activeSuggestions | the object of fields with active suggestions | { fieldName: SuggestionInstance[] } | | activeFilters | the object of fields with active filters | { fieldName: FilterInstance[] } | | fieldsWithFiltersAndSuggestions | the object of fields and the filters and suggestions that are available for them | { fieldName: { suggestions: SuggestionInstance[], filters: FilterInstance[] } } | | hasNextPage | Whether another page is available via the nextPage method | boolean |

Common Among All Filters

Initialization

All filter constructors have the signature (defaultConfig, specificConfig) => FilterTypeInstance

defaultConfig and specificConfig are specific to each filter class type.

Methods

| method | description | type | | --------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------- | | setFilter | sets the filter for a field | (field: <name of field>, filter: <filter specific to filter class type>): void | | clearFilter | clears the filter for a field | (field: <name of field>): void | | setKind | sets the kind for a field | (field: <name of field>, kind: should or must): void | | setAggsEnabledToTrue | enables fetching of aggs for this filter field | (field: <name of field>): void | | setAggsEnabledToFalse | disables fetching of aggs for this filter field | (field: <name of field>): void |

Attributes

| attribute | description | type | | ------------ | ------------------------------------------------------------ | ----------------------------------------------------------------- | | fieldConfigs | the config for a field, keyed by field name | { [<names of fields>]: <config specific to filter class type> } | | fieldFilters | the filters for a field, keyed by field name | { [<names of fields>]: Filter } | | fieldKinds | the kind (should or must) for a field, keyed by field name | { [<names of fields>]: 'should' or 'must' } |

Boolean Specific

Initialization

The boolean constructor has the signature (defaultConfig, specificConfig) => BooleanFilterInstance

defaultConfig

The configuration that each field will acquire if an override is not specifically set in specificConfig

type DefaultConfig = {
    defaultFilterKind: 'should',
    getCount: true,
    aggsEnabled: false
};
specificConfig

The explicit configuration set on a per field level. If a config isn't specified or only partially specified for a field, the defaultConfig will be used to fill in the gaps.

type SpecificConfig = Record<string, BooleanConfig>;

type BooleanConfig = {
    field: string;
    defaultFilterKind?: 'should' | 'must';
    getCount?: boolean;
    aggsEnabled?: boolean;
};

Methods

| method | description | type | | --------- | --------------------------- | ------------------------------------------------------------------------ | | setFilter | sets the filter for a field | (field: <name of boolean field>, filter: {state: true or false}): void |

Attributes

| attribute | description | type | | --------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------ | | filteredCount | the count of boolean values of all filtered documents, keyed by field name | { [<names of boolean fields>]: { true: number; false: number;} } | | unfilteredCount | the count of boolean values of all unfiltered documents, keyed by field name | { [<names of boolean fields>]: { true: number; false: number;} } |

Range Specific

Initialization

The range constructor has the signature (defaultConfig, specificConfig) => RangeFilterInstance

defaultConfig

The configuration that each field will acquire if an override is not specifically set in specificConfig

type RangeConfig = {
    defaultFilterKind: 'should',
    getDistribution: true,
    getRangeBounds: true,
    rangeInterval: 1,
    aggsEnabled: false
};
specificConfig

The explicit configuration set on a per field level. If a config isn't specified or only partially specified for a field, the defaultConfig will be used to fill in the gaps.

type SpecificConfig = Record<string, RangeConfig>;

type RangeConfig = {
    field: string;
    defaultFilterKind?: 'should' | 'must';
    getDistribution?: boolean;
    getRangeBounds?: boolean;
    rangeInterval?: number;
    aggsEnabled?: boolean;
};

Methods

| method | description | type | | --------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | setFilter | sets the filter for a field | (field: <name of range field>, filter: {lessThan?: number, greaterThan?: number, lessThanEqual?: number, greaterThanEqual?: number): void |

Attributes

| attribute | description | type | | ---------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | filteredRangeBounds | the bounds of all filtered ranges (ex: 20 - 75), keyed by field name | { [<names of range fields>]: { min: { value: number; value_as_string?: string; }; max: { value: number; value_as_string?: string; };} } | | unfilteredRangeBounds | the bounds of all unfiltered ranges (ex: 0 - 100), keyed by field name | { [<names of range fields>]: { min: { value: number; value_as_string?: string; }; max: { value: number; value_as_string?: string; };} } | | filteredDistribution | the distribution of all filtered ranges, keyed by field name | {[<names of range fields>]: Array<{ key: number; doc_count: number; }>} | | unfilteredDistribution | the distribution of all filtered ranges, keyed by field name | {[<names of range fields>]: Array<{ key: number; doc_count: number; }>} |

Exists Specific

Initialization

The exists constructor has the signature (defaultConfig, specificConfig) => ExistsFilterInstance

defaultConfig

The configuration that each field will acquire if an override is not specifically set in specificConfig

type DefaultConfig = {
   defaultFilterKind: 'should',
   getCount: true,
   aggsEnabled: false
};
specificConfig

The explicit configuration set on a per field level. If a config isn't specified or only partially specified for a field, the defaultConfig will be used to fill in the gaps.

type SpecificConfig = Record<string, ExistsConfig>;

type ExistsConfig = {
    field: string;
    defaultFilterKind?: 'should' | 'must';
    getCount?: boolean;
    aggsEnabled?: boolean;
};

Methods

| method | description | type | | --------- | --------------------------- | ------------------------------------------------------------------------ | | setFilter | sets the filter for a field | (field: <name of exists field>, filter: {exists: true or false}): void |

Attributes

| attribute | description | type | | --------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | filteredCount | the count of exists values of all filtered documents, keyed by field name | { [<names of exists fields>]: { exists: number; doesntExist: number;} } | | unfilteredCount | the count of exists values of all unfiltered documents, keyed by field name | { [<names of exists fields>]: { exists: number; doesntExist: number;} } |

Multi-Select Specific

Initialization

The multiselect constructor has the signature (defaultConfig, specificConfig) => MultiSelectFilterInstance

defaultConfig

The configuration that each field will acquire if an override is not specifically set in specificConfig

type DefaultConfig = {
    defaultFilterKind: 'should',
    defaultFilterInclusion: 'include',
    defaultMatch: 'match',
    getCount: true,
    aggsEnabled: false,
    fieldNameModifierQuery: (fieldName: string) => fieldName
    fieldNameModifierAggs: (fieldName: string) => fieldName
};
specificConfig

The explicit configuration set on a per field level. If a config isn't specified or only partially specified for a field, the defaultConfig will be used to fill in the gaps.

type SpecificConfig = Record<string, MultiSelectConfig>;

type MultiSelectConfig = {
    field: string;
    defaultFilterKind?: 'should' | 'must';
    defaultFilterInclusion?: 'include' | 'exclude';
    defaultMatch?: 'match' | 'match_phrase';
    getCount?: boolean;
    aggsEnabled?: boolean;
    fieldNameModifierQuery?: (fieldName: string) => string
    fieldNameModifierAggs?: (fieldName: string) => string
};

NOTE: fieldNameModifier functions will be overwritten to (fieldName: string) => ${fieldName}.keyword if match_phrase is specified as match.

Geo Specific

NOTE: Geo filters do not have any aggs enabled! Do not try to use aggs with geo filters.

Examples of GeoFilter actions and the queries they generate can be found at src/filters/geo_filter_README.md

Initialization

The geoFilter constructor has the signature (defaultConfig, specificConfig) => GeoFilterInstance

defaultConfig

The configuration that each field will acquire if an override is not specifically set in specificConfig

type DefaultConfig = {
    defaultFilterKind: 'should',
    defaultFilterInclusion: 'include',
    getCount: true,
    aggsEnabled: false,
    fieldNameModifierQuery: (fieldName: string) => fieldName
    fieldNameModifierAggs: (fieldName: string) => fieldName
};
specificConfig

The explicit configuration set on a per field level. If a config isn't specified or only partially specified for a field, the defaultConfig will be used to fill in the gaps.

type SpecificConfig = Record<string, GeoConfig>;

type GeoConfig = {
    field: string;
    defaultFilterKind?: 'should' | 'must';
    defaultFilterInclusion?: 'include' | 'exclude';
    getCount?: boolean;
    aggsEnabled?: boolean;
    fieldNameModifierQuery?: (fieldName: string) => string
    fieldNameModifierAggs?: (fieldName: string) => string
};

Methods

A filter selection has the type:

{
  inclusion: 'include' | 'exclude';
  kind?: 'should' | 'must';
}

| method | description | type | | ---------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | setFilter | sets the filter for a field | (field: <name of geo field>, filter: {[geoSubFilterReferenceName]: {inclusion: 'include' or 'exclude', kind?: 'should' or 'must'}}): void | | addToFilter | adds a single selection to a filter | addToFilter(field: <name of geo field>, geoSubFilterReferenceName: string, selectionFilter: {inclusion: 'include' or 'exclude', kind?: 'should' or 'must'}): void | | removeFromFilter | removes a single selection from a filter | removeFromFilter(field: <name of geo field>, geoSubFilterReferenceName: string): void |

Attributes

| attribute | description | type | | --------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |

Note: No aggregates are implemented, thus there are no attributes specific to this filter type.

Common Among All Suggestions

The suggestions that ship with this package all have the same public interface (for the moment). Thus, you can rely on this section for API documentation on each suggestion type.

Initialization

All filter constructors have the signature (defaultConfig, specificConfig) => SuggestionTypeInstance

defaultConfig and specificConfig are specific to each suggestion class type.

The defaultConfig looks like:

{
    defaultSuggestionKind: 'should',
    enabled: false,
    fieldNameModifierQuery: (fieldName: string) => fieldName,
    fieldNameModifierAggs: (fieldName: string) => fieldName
}

The typings for the specific config object looks like:

{
    field: string;
    defaultSuggestionKind?: 'should' | 'must';
    enabled?: boolean;
    fieldNameModifierQuery?: FieldNameModifier;
    fieldNameModifierAggs?: FieldNameModifier;
}

Methods

| method | description | type | | ----------------- | ---------------------------------------------------------- | ------------------------------------------------------ | | setSearch | sets the search term for a field to get suggestions for | (field: <name of field>, searchTerm: string): void | | clearSearch | clears the search for a field | (field: <name of field>): void | | setKind | sets the kind for a field | (field: <name of field>, kind: should or must): void | | setEnabledToTrue | enables fetching of suggestions for this suggestion field | (field: <name of field>): void | | setEnabledToFalse | disables fetching of suggestions for this suggestion field | (field: <name of field>): void |

Attributes

| attribute | description | type | | ---------------- | ------------------------------------------------------------ | --------------------------------------------------------------------- | | fieldConfigs | the config for a field, keyed by field name | { [<names of fields>]: <config specific to filter class type> } | | fieldSuggestions | the suggestions for a field, keyed by field name | { [<names of fields>]: Array<{suggestion: string; count: number}> } | | fieldSearches | the searches for a field, keyed by field name | { [<names of fields>]: string } | | fieldKinds | the kind (should or must) for a field, keyed by field name | { [<names of fields>]: 'should' or 'must' } |

History API

The history API allows you to:

  • record history of user interactions with filters and suggestions
  • allow you to serialize the current state to a URL query param
  • allow you to save the history to local storage and rehydrate from this storage.

Initialization

All filter constructors have the signature (manager: Manager, queryParamKey: string, options?: IHistoryOptions<HistoryLocation>) => SuggestionTypeInstance

The options looks like:

{
    historySize?: number;
    currentLocationStore?: UrlStore<State>;
    historyPersister?: IHistoryPersister;
    rehydrateOnStart?: boolean; // whether to run the `rehydrate` method in the constructor
}

In turn, the historyPersister has the type:

IHistoryPersister {
    setHistory: (location: Array<HistoryLocation | undefined>) => void;
    getHistory: () => HistoryLocation[];
}

Methods

| method | description | type | | ----------------- | ---------------------------------------------------------- | ------------------------------------------------------ | | setCurrentState | sets the current state of filters and suggestgions | (location: HistoryLocation): void | | back | goes back in the history | (): void | | forward | goes forward in the history | (): void | | rehydrate | rehydrates from URL or persistent storage | (): void |

Attributes

| attribute | description | type | | ---------------- | ------------------------------------------------------------ | --------------------------------------------------------------------- | | history | the recorded history | Array<HistoryLocation | undefined> | | currentLocationInHistoryCursor | the location in history, changed by going 'back' or 'forward' | number | | hasRehydratedLocation | flag to tell if any location was rehydrated from when the rehydrate method was called |

Verbose Examples

See ./dev/app/ for examples used in the development environment.

Usage with React

import {AxiosESClient, Manager} from 'elastic-composer';

const client = new AxiosESClient(process.env.ELASTIC_SEARCH_ENDPOINT);
const creatorCRM = new Manager(client);

creatorCRM.getFieldNamesAndTypes().then(() => {
    creatorCRM.runStartQuery();
});

export default {
    exampleForm: React.createContext(exampleFormInstance),
    creatorCRM: React.createContext(creatorCRM)
};

Extending Filters and Suggestions

TODO