Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Updated comments on AppSearchDriver
  • Loading branch information
JasonStoltz committed Oct 29, 2018
commit f50527d36d7c87e74ca132bf5c6f6205da8c3944
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ You can follow the previous steps, but then you will need to configure
[engine.json](src/config/engine.json).

To do so, make a copy of [engine.json.example](src/config/engine.json.example),
rename it to `engine.json` and configure with your Engine's specific details.
rename it to `engine.json` and configure it with your Engine's specific details.

```bash
cp src/config/engine.json.example src/config/engine.json
Expand Down Expand Up @@ -117,7 +117,14 @@ That corresponds to the code and file structure in the following way:

**src/app-search**

This holds the `AppSearchDriver`, the `URLManager`, and `AppSearchProvider`
Everything in this directory for now should be thought of as a separate library.
The goal eventually is to actually separate this out into a library of its own,
so when composing a UI you'd simply need to focus on creating components
from actions and state, and not all of the plumbing that goes into managing
that state. For now though, it's included in this reference as a pattern
that can be followed.

This holds the `SearchDriver`, the `URLManager`, and `SearchProvider`
from the diagram above. This is where all of the core application logic lives.
The interface to all of this logic is a set of "actions" and "state" that are
passed down in a React [Context](https://reactjs.org/docs/context.html). Those
Expand Down Expand Up @@ -158,13 +165,6 @@ So, for instance, a `SearchBox` component might be wired up to call the
value. A `Results` component could then simply iterate through the `results`
from state to render search results.

Everything in this directory for now should be thought of as a separate library.
The goal eventually is to actually separate this out into a library of it's own,
so when composing a UI you'd simply need to focus on creating components
from actions and state, and not all of the plumbing that goes into managing
that state. For now though, it's included in this reference as a pattern
that can be followed.

**src/containers**

Components in this UI are separated into "Containers" and "Components". These
Expand Down Expand Up @@ -195,16 +195,28 @@ It should be feasible to use this project as a starting point for your
own implementation. Here are a few places to look to make changes:

- The styles for the entire project can be found in [src/styles](src/styles).
Simple style tweaks changes can be made here, or you could replace these styles
with your own.
Simple style tweaks can be made here, or you could replace these styles
entirely with your own.
- [src/components](src/components) contains the view templates for
components. Structural HTML changes can be made here.
- If you find that you have different data or behavior requirements for
existing components, you can customize the component Containers in
[src/containers](src/containers).
- If you find you have requirements that none of the existing components
satisfy, you could create an entirely new component and/or container. Use the
`withAppSearch` HOC in order to access any action or state.
[withSearch.js](src/search-lib/withSearch.js) HOC in order to access any action or state.
- The SearchDriver can be configured directly in [App.js](src/App.js) to do things like:

- Optimize your API calls
- Add additional facets and customize facet behavior
- Disable URL State management

A full list of configuration options can be found in [SearchDriver.js](src/search-lib/SearchDriver.js)

- Eject from 'configuration'. You may choose to to delete the entire [src/config](src/config) directory, which holds the configuration logic that makes this a
generic UI. If you're using this as your own, production application, you likely
won't need this.

- Lastly, if you find there is a core action or state missing, you may
consider updating the core logic in [src/app-search](src/app-search).

Expand Down
14 changes: 12 additions & 2 deletions src/app-search/AppSearchAPIConnector.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import * as SwiftypeAppSearch from "swiftype-app-search-javascript";

/*
* We mainly just need to filter out unsupported configuration values, like
* `disjunctive`
* We simply pass our Facet configuration through to the App Search API
* call. There are, however, certain properties that the API does not
* support in that configuration. For that reason, we need to filter
* those properties out before passing them to the API.
*
* An example is 'disjunctive', and 'conditional'.
*/
function toAPIFacetSyntax(facetConfig = {}) {
return Object.entries(facetConfig).reduce((acc, [key, value]) => {
Expand All @@ -16,6 +20,12 @@ function toAPIFacetSyntax(facetConfig = {}) {
}, {});
}

/*
* 'disjunctive' flags are embedded in individual facet configurations, like
* so:
*
* facets
*/
function getListOfDisjunctiveFacets(facetConfig = {}) {
return Object.entries(facetConfig).reduce((acc, [key, value]) => {
if (value && value.disjunctive === true) {
Expand Down
101 changes: 72 additions & 29 deletions src/app-search/AppSearchDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,17 @@ function removeSingleFilterValue(filters, name, value) {
}

export const DEFAULT_STATE = {
// Search Parameters -- This is state that represents the input that was
// used to produce the current query results. It is always in sync
// with the Results State
// Search Parameters -- This is state that represents the input state.
current: 1,
error: "",
filters: [],
isLoading: false,
resultsPerPage: 20,
searchTerm: "",
sortDirection: "",
sortField: "",
// Results State -- This state represents the results of the current query
// Result State -- This state represents state that is updated automatically
// as the result of changing input state.
error: "",
isLoading: false,
facets: {},
requestId: "",
results: [],
Expand All @@ -60,7 +59,7 @@ export const DEFAULT_STATE = {
};

/*
* This temporarily fixes a core issue we have with filtering.
* This fixes an issue with filtering.
* Our data structure for filters are the "OR" format for the App Search
* API:
*
Expand All @@ -83,9 +82,6 @@ export const DEFAULT_STATE = {
* ]
* }
* ```
*
* Ultimately, we should choose our own data structures in the driver
* that don't mirror the API. But they will need to support AND vs OR.
*/
function formatORFiltersAsAND(filters = []) {
return filters.reduce((acc, filter) => {
Expand All @@ -96,9 +92,9 @@ function formatORFiltersAsAND(filters = []) {
}

/*
* There's this weird thing where facet values for dates come back as an integer
* from the API, but the API expects them as parameters formatted as date
* strings.
* Facet values for dates come back as Integer from the API. However, the API
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

* expects them as a formatted date String when applying that same value
* as a filter.
*/
function convertRangeFiltersToDateString(filters = []) {
const val = filters.map(filter => {
Expand Down Expand Up @@ -154,31 +150,78 @@ function matchFilter(filter1, filter2) {
* it is the source of truth for state in this React App, but it has no
* dependencies on React itself.
*
* The public interface of the Driver can be thought about in the following
* way:
* The public interface of the Driver can be thought about as "state" and
* "actions."
*
* Ways to GET state:
* - getState - Get the initial app state
* - subscribeToStateChanges - Get updated state whenever it changes
* - subscribeToStateChanges - Get updated state whenever it changes.
*
* Ways to SET state, using actions. All actions can be found in 'getActions'.
*
* const {addFilter} = getActions().
*
* addFilter, and most actions, will typically update the state and trigger
* new queries to be run against the search API.
*
* Configuration:
*
* - apiConnector: APIConnector
* Instance of an API Connector. For instance, AppSearchAPIConnector
*
* Ways to SET state, or "Actions" as we refer to them elsewhere
* - addFilter, etc, will typically update the state and trigger new queries
* - facetConfig: Facet
* Configuration for Facet filters to be used within this application. The
* syntax for Facet configuration follows the API syntax:
* https://swiftype.com/documentation/app-search/api/search/facets. In
* addition to the options provided by the API, the following per Facet
* configuration is also available:
* - conditional[function]
* This facet will only be applied if the condition specified returns
* true, based on the current applied filters.
* - disjunctive[boolean]
* When returning counts for disjunctive facets, the counts will be
* returned as if no filter is applied on this field, even if one is
* applied. A common use case for this is tabbed filters.
*
* ex.
* facetConfig: {
* author: {
* type: "value",
* size: 40,
* disjunctive: true,
* conditional: ({ filters }) =>
* ["blog", "videos"].includes(filters.filter(f => f["website_area"]))
* }
* }
*
* - initialState: Object
* Set initial input state, or search parameters. For example, initializing
* the search page with certain parameters already set:
*`
* initialState: {
* searchTerm: "test",
* resultsPerPage: 40
* }
*
* Valid search parameters are:
* current: Integer
* filters: Array[Object]
* resultsPerPage: Integer
* searchTerm: String
* sortDirection: String ["asc"|"desc"]
* sortField: String
*
* - searchOptions: Object
* This is low level configuration which lets you configure
* the options used on the Search API endpoint, ex: `result_fields`.
* https://swiftype.com/documentation/app-search/api/search
*
* - trackURLState: Boolean
* URL State management can be disabled completely
*/
export default class AppSearchDriver {
state = DEFAULT_STATE;

/**
*
* @param options Object
* apiConnector - Connector for a particular search API
* facetConfig - Configuration for facets, based on format specified in API documentation
* initialState - This lets you set initial search parameters, ex:
* `searchTerm: "test"`
* searchOptions - A low level configuration which lets you configure
* the options used on the Search API endpoint, ex: `result_fields`
* trackURLState - Boolean, track state in the url or not?
*/
constructor({
apiConnector,
facetConfig,
Expand Down