Skip to content

Commit f50527d

Browse files
committed
Updated comments on AppSearchDriver
1 parent f6f3667 commit f50527d

File tree

3 files changed

+108
-43
lines changed

3 files changed

+108
-43
lines changed

README.md

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ You can follow the previous steps, but then you will need to configure
7171
[engine.json](src/config/engine.json).
7272

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

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

118118
**src/app-search**
119119

120-
This holds the `AppSearchDriver`, the `URLManager`, and `AppSearchProvider`
120+
Everything in this directory for now should be thought of as a separate library.
121+
The goal eventually is to actually separate this out into a library of its own,
122+
so when composing a UI you'd simply need to focus on creating components
123+
from actions and state, and not all of the plumbing that goes into managing
124+
that state. For now though, it's included in this reference as a pattern
125+
that can be followed.
126+
127+
This holds the `SearchDriver`, the `URLManager`, and `SearchProvider`
121128
from the diagram above. This is where all of the core application logic lives.
122129
The interface to all of this logic is a set of "actions" and "state" that are
123130
passed down in a React [Context](https://reactjs.org/docs/context.html). Those
@@ -158,13 +165,6 @@ So, for instance, a `SearchBox` component might be wired up to call the
158165
value. A `Results` component could then simply iterate through the `results`
159166
from state to render search results.
160167

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

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

197197
- The styles for the entire project can be found in [src/styles](src/styles).
198-
Simple style tweaks changes can be made here, or you could replace these styles
199-
with your own.
198+
Simple style tweaks can be made here, or you could replace these styles
199+
entirely with your own.
200200
- [src/components](src/components) contains the view templates for
201201
components. Structural HTML changes can be made here.
202202
- If you find that you have different data or behavior requirements for
203203
existing components, you can customize the component Containers in
204204
[src/containers](src/containers).
205205
- If you find you have requirements that none of the existing components
206206
satisfy, you could create an entirely new component and/or container. Use the
207-
`withAppSearch` HOC in order to access any action or state.
207+
[withSearch.js](src/search-lib/withSearch.js) HOC in order to access any action or state.
208+
- The SearchDriver can be configured directly in [App.js](src/App.js) to do things like:
209+
210+
- Optimize your API calls
211+
- Add additional facets and customize facet behavior
212+
- Disable URL State management
213+
214+
A full list of configuration options can be found in [SearchDriver.js](src/search-lib/SearchDriver.js)
215+
216+
- 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
217+
generic UI. If you're using this as your own, production application, you likely
218+
won't need this.
219+
208220
- Lastly, if you find there is a core action or state missing, you may
209221
consider updating the core logic in [src/app-search](src/app-search).
210222

src/app-search/AppSearchAPIConnector.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import * as SwiftypeAppSearch from "swiftype-app-search-javascript";
22

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

23+
/*
24+
* 'disjunctive' flags are embedded in individual facet configurations, like
25+
* so:
26+
*
27+
* facets
28+
*/
1929
function getListOfDisjunctiveFacets(facetConfig = {}) {
2030
return Object.entries(facetConfig).reduce((acc, [key, value]) => {
2131
if (value && value.disjunctive === true) {

src/app-search/AppSearchDriver.js

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,17 @@ function removeSingleFilterValue(filters, name, value) {
3939
}
4040

4141
export const DEFAULT_STATE = {
42-
// Search Parameters -- This is state that represents the input that was
43-
// used to produce the current query results. It is always in sync
44-
// with the Results State
42+
// Search Parameters -- This is state that represents the input state.
4543
current: 1,
46-
error: "",
4744
filters: [],
48-
isLoading: false,
4945
resultsPerPage: 20,
5046
searchTerm: "",
5147
sortDirection: "",
5248
sortField: "",
53-
// Results State -- This state represents the results of the current query
49+
// Result State -- This state represents state that is updated automatically
50+
// as the result of changing input state.
51+
error: "",
52+
isLoading: false,
5453
facets: {},
5554
requestId: "",
5655
results: [],
@@ -60,7 +59,7 @@ export const DEFAULT_STATE = {
6059
};
6160

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

9894
/*
99-
* There's this weird thing where facet values for dates come back as an integer
100-
* from the API, but the API expects them as parameters formatted as date
101-
* strings.
95+
* Facet values for dates come back as Integer from the API. However, the API
96+
* expects them as a formatted date String when applying that same value
97+
* as a filter.
10298
*/
10399
function convertRangeFiltersToDateString(filters = []) {
104100
const val = filters.map(filter => {
@@ -154,31 +150,78 @@ function matchFilter(filter1, filter2) {
154150
* it is the source of truth for state in this React App, but it has no
155151
* dependencies on React itself.
156152
*
157-
* The public interface of the Driver can be thought about in the following
158-
* way:
153+
* The public interface of the Driver can be thought about as "state" and
154+
* "actions."
159155
*
160156
* Ways to GET state:
161157
* - getState - Get the initial app state
162-
* - subscribeToStateChanges - Get updated state whenever it changes
158+
* - subscribeToStateChanges - Get updated state whenever it changes.
159+
*
160+
* Ways to SET state, using actions. All actions can be found in 'getActions'.
161+
*
162+
* const {addFilter} = getActions().
163+
*
164+
* addFilter, and most actions, will typically update the state and trigger
165+
* new queries to be run against the search API.
166+
*
167+
* Configuration:
168+
*
169+
* - apiConnector: APIConnector
170+
* Instance of an API Connector. For instance, AppSearchAPIConnector
163171
*
164-
* Ways to SET state, or "Actions" as we refer to them elsewhere
165-
* - addFilter, etc, will typically update the state and trigger new queries
172+
* - facetConfig: Facet
173+
* Configuration for Facet filters to be used within this application. The
174+
* syntax for Facet configuration follows the API syntax:
175+
* https://swiftype.com/documentation/app-search/api/search/facets. In
176+
* addition to the options provided by the API, the following per Facet
177+
* configuration is also available:
178+
* - conditional[function]
179+
* This facet will only be applied if the condition specified returns
180+
* true, based on the current applied filters.
181+
* - disjunctive[boolean]
182+
* When returning counts for disjunctive facets, the counts will be
183+
* returned as if no filter is applied on this field, even if one is
184+
* applied. A common use case for this is tabbed filters.
166185
*
186+
* ex.
187+
* facetConfig: {
188+
* author: {
189+
* type: "value",
190+
* size: 40,
191+
* disjunctive: true,
192+
* conditional: ({ filters }) =>
193+
* ["blog", "videos"].includes(filters.filter(f => f["website_area"]))
194+
* }
195+
* }
196+
*
197+
* - initialState: Object
198+
* Set initial input state, or search parameters. For example, initializing
199+
* the search page with certain parameters already set:
200+
*`
201+
* initialState: {
202+
* searchTerm: "test",
203+
* resultsPerPage: 40
204+
* }
205+
*
206+
* Valid search parameters are:
207+
* current: Integer
208+
* filters: Array[Object]
209+
* resultsPerPage: Integer
210+
* searchTerm: String
211+
* sortDirection: String ["asc"|"desc"]
212+
* sortField: String
213+
*
214+
* - searchOptions: Object
215+
* This is low level configuration which lets you configure
216+
* the options used on the Search API endpoint, ex: `result_fields`.
217+
* https://swiftype.com/documentation/app-search/api/search
218+
*
219+
* - trackURLState: Boolean
220+
* URL State management can be disabled completely
167221
*/
168222
export default class AppSearchDriver {
169223
state = DEFAULT_STATE;
170224

171-
/**
172-
*
173-
* @param options Object
174-
* apiConnector - Connector for a particular search API
175-
* facetConfig - Configuration for facets, based on format specified in API documentation
176-
* initialState - This lets you set initial search parameters, ex:
177-
* `searchTerm: "test"`
178-
* searchOptions - A low level configuration which lets you configure
179-
* the options used on the Search API endpoint, ex: `result_fields`
180-
* trackURLState - Boolean, track state in the url or not?
181-
*/
182225
constructor({
183226
apiConnector,
184227
facetConfig,

0 commit comments

Comments
 (0)