Skip to content

Commit

Permalink
feat(@dpc-sdp/ripple-tide-search): added support for function filters…
Browse files Browse the repository at this point in the history
…, grantStatus function filter
  • Loading branch information
jeffdowdle committed Jul 18, 2023
1 parent f6c02a2 commit 33f42b2
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 16 deletions.
11 changes: 11 additions & 0 deletions examples/nuxt-app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ export default defineAppConfig({
'linear-gradient(90deg, #382484 0%, #5A0099 20%, #7623B0 35%, #2E7478 50%, #2FA26F 70%, #2FCE6A 80%)',
'rpl-clr-gradient-vertical':
'linear-gradient(180deg, #382484 0%, #5A0099 20%, #7623B0 35%, #2E7478 50%, #2FA26F 70%, #2FCE6A 80%)'
},
search: {
filterFunctions: {
// `dummyFunctionFilter` is used in a cypress test to check that the correct parameters are passed to custom filter functions
dummyFunctionFilter: (filterConfig, values) => {
return {
providedFilterConfig: filterConfig,
providedValues: values
}
}
}
}
}
})
40 changes: 31 additions & 9 deletions examples/nuxt-app/test/features/search-listing/filters.feature
Original file line number Diff line number Diff line change
Expand Up @@ -125,25 +125,47 @@ Feature: Search listing - Filter
| Oranges |

@mockserver
Example: Should update the URL when the filters are applied
Example: Custom function filters - Should reflect an array from the URL
Given the endpoint "/api/tide/page" with query "?path=/filters&site=8888" returns fixture "/search-listing/filters/page" with status 200
And the search network request is stubbed with fixture "/search-listing/filters/response" and status 200
And the current date is "Fri, 02 Feb 2050 03:04:05 GMT"

When I visit the page "/filters?termsFilter=Purple&termsFilter=Orange"
When I visit the page "/filters?functionFilter=closed&functionFilter=open"
Then the search listing page should have 2 results
And the search network request should be called with the "/search-listing/filters/request-terms-array" fixture
And the search network request should be called with the "/search-listing/filters/request-function-filter" fixture

Then the search listing dropdown field labelled "Terms filter example" should have the value "Orange, Purple"
When I click the search listing dropdown field labelled "Terms filter example"
Then the search listing dropdown field labelled "Custom function filter example" should have the value "Open, Closed"
When I click the search listing dropdown field labelled "Custom function filter example"
Then the selected dropdown field should have the items:
| Orange |
| Purple |
| Yellow |
| Open |
| Closed |
# Close the dropdown
When I click the search listing dropdown field labelled "Terms filter example"
When I click the search listing dropdown field labelled "Custom function filter example"
And the search listing results should have following items:
| title |
| Apples |
| Oranges |

# @mockserver
# Example: Should update the URL when the filters are applied
# Given the endpoint "/api/tide/page" with query "?path=/filters&site=8888" returns fixture "/search-listing/filters/page" with status 200
# And the search network request is stubbed with fixture "/search-listing/filters/response" and status 200
# And the current date is "Fri, 02 Feb 2050 03:04:05 GMT"

# When I visit the page "/filters?termsFilter=Purple&termsFilter=Orange"
# Then the search listing page should have 2 results
# And the search network request should be called with the "/search-listing/filters/request-terms-array" fixture

# Then the search listing dropdown field labelled "Terms filter example" should have the value "Orange, Purple"
# When I click the search listing dropdown field labelled "Terms filter example"
# Then the selected dropdown field should have the items:
# | Orange |
# | Purple |
# | Yellow |
# # Close the dropdown
# When I click the search listing dropdown field labelled "Terms filter example"
# And the search listing results should have following items:
# | title |
# | Apples |
# | Oranges |

26 changes: 26 additions & 0 deletions examples/nuxt-app/test/fixtures/search-listing/filters/page.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,32 @@
}
]
}
},
{
"id": "functionFilter",
"component": "TideSearchFilterDropdown",
"filter": {
"type": "function",
"value": "dummyFunctionFilter"
},
"props": {
"id": "functionFilter",
"label": "Custom function filter example",
"placeholder": "Select a grant status",
"multiple": true,
"options": [
{
"id": "open",
"label": "Open",
"value": "open"
},
{
"id": "closed",
"label": "Closed",
"value": "closed"
}
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": [
{
"terms": {
"type": ["grant"]
}
},
{
"terms": {
"field_node_site": [8888]
}
},
{
"providedFilterConfig": {
"id": "functionFilter",
"component": "TideSearchFilterDropdown",
"filter": {
"type": "function",
"value": "dummyFunctionFilter"
},
"props": {
"id": "functionFilter",
"label": "Custom function filter example",
"placeholder": "Select a grant status",
"multiple": true,
"options": [
{
"id": "open",
"label": "Open",
"value": "open"
},
{
"id": "closed",
"label": "Closed",
"value": "closed"
}
]
}
},
"providedValues": ["closed", "open"]
}
]
}
},
"size": 10,
"from": 0,
"sort": [
{
"_score": "desc"
},
{
"_doc": "desc"
}
]
}
37 changes: 37 additions & 0 deletions examples/nuxt-app/test/fixtures/search-listing/grants/page.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,43 @@
}
]
}
},
{
"id": "status",
"component": "TideSearchFilterDropdown",
"filter": {
"type": "function",
"value": "grantStatus"
},
"props": {
"id": "status",
"label": "Status",
"placeholder": "Select for opened, closed or upcoming grants",
"type": "RplFormDropdown",
"multiple": true,
"options": [
{
"id": "open",
"label": "Open",
"value": "open"
},
{
"id": "closed",
"label": "Closed",
"value": "closed"
},
{
"id": "ongoing",
"label": "Ongoing",
"value": "ongoing"
},
{
"id": "opening_soon",
"label": "Opening soon",
"value": "opening_soon"
}
]
}
}
]
}
6 changes: 6 additions & 0 deletions packages/nuxt-ripple/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ declare module '@nuxt/schema' {
['rpl-clr-focus']?: string
['rpl-clr-type-focus-contrast']?: string
}
search?: {
filterFunctions?: Record<
string,
(filterConfig: any, values: string[]) => void
>
}
}
}
}
Expand Down
71 changes: 71 additions & 0 deletions packages/ripple-tide-grant/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export default defineAppConfig({
ripple: {
search: {
filterFunctions: {
grantStatus: (filterConfig: string, statuses: unknown) => {
const mapStatusToFilterDSL = (status) => {
switch (status) {
case 'open':
return {
bool: {
must: [
{
range: {
field_node_dates_start_value: {
lte: 'now'
}
}
},
{
range: {
field_node_dates_end_value: {
gte: 'now'
}
}
}
]
}
}
case 'closed':
return {
range: {
field_node_dates_end_value: {
lte: 'now'
}
}
}

case 'ongoing':
return {
term: {
field_node_on_going: 'true'
}
}

case 'opening_soon':
return {
range: {
field_node_dates_start_value: {
gte: 'now'
}
}
}
default:
return null
}
}

const filters = statuses
.map(mapStatusToFilterDSL)
.filter((query) => !!query) // filter null values

return {
bool: {
should: filters
}
}
}
}
}
}
})
47 changes: 40 additions & 7 deletions packages/ripple-tide-search/composables/useTideSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default (
) => {
const { public: config } = useRuntimeConfig()
const route: RouteLocation = useRoute()
const appConfig = useAppConfig()

const index = customIndex || config.tide.appSearch.engineName

Expand Down Expand Up @@ -120,26 +121,33 @@ export default (
const userFilters = computed(() => {
return Object.keys(filterForm.value).map((key: string) => {
const itm = userFilterConfig.find((itm) => itm.id === key)

const filterVal =
filterForm.value[key] && Array.from(filterForm.value[key])

// Need to work out if form has value - will be different for different controls
const hasValue = (v: unknown) => {
if (itm.component === 'TideSearchFilterDropdown') {
return Array.isArray(v) && v.length > 0
}
return v
}

if (itm.filter && hasValue(filterVal)) {
// Raw ES Filter clause from Tide config, replaces {{value}} with user value
/**
* Raw ES Filter clause from Tide config, replaces {{value}} with user value
*/
if (itm.filter.type === 'raw') {
const re = new RegExp('{{value}}', 'g')
const result = itm.filter.value.replace(re, JSON.stringify(filterVal))
return JSON.parse(result)
}

// Term and Terms querys - To simplify things we transform all term queries into terms queries with a single value array
// - Term query: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html
// - Terms query: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html
/**
* Term and Terms querys - To simplify things we transform all term queries into terms queries with a single value array
* - Term query: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html
* - Terms query: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html
*/
if (itm.filter.type === 'term' || itm.filter.type === 'terms') {
return {
terms: {
Expand All @@ -150,10 +158,35 @@ export default (
}
}

// Call a function passed from app.config to add filters
/**
* Call a function passed from app.config to to allow extending and overriding. The function should
* return a valid DSL query.
* When called the function is passed the filter config and the value of the filter from the user
*
* In the nuxt app.config, the function is provided like this:
* {
* ripple: {
* search: {
exampleFunction: (filterConfig, values) => {
return {
... some DSL query
}
}
* }
* }
* }
*/
if (itm.filter.type === 'function') {
// TODO: this should allow calling a custom function that returns a valid query clause
// function should be passed through from app.config to allow extending and overriding
const filterFuncs = appConfig?.ripple?.search?.filterFunctions || {}
const fn = filterFuncs[itm.filter.value]

if (typeof fn !== 'function') {
throw new Error(
`Search listing: No matching filter function called "${itm.filter.value}"`
)
}

return fn(itm, filterVal)
}
}
})
Expand Down

0 comments on commit 33f42b2

Please sign in to comment.