Skip to content

Commit

Permalink
feat: Implement Query Rules v2
Browse files Browse the repository at this point in the history
This commit adds changes following the release of Query Rules v2. All
changes are described in details in the following dedicated sections.

**Enabled flag**
A new `Enabled` boolean field was added to the `Rule` type. However, one
must use the new `Rule.Disable()` and `Rule.Enable()` methods to disable
or enable a rule respectively. While setting `Enabled` to `true`
manually would effectively enable the rule, setting to `false` would
have no effect, the rule would still be implicitly enabled. This is due
to the fact that Go booleans default to `false` when uninitialised and
because the implementation has to export the field (i.e. making it
public) to be able to properly deserialize it.

**Time windows**
A new `Validity` field was added to the `Rule` type. It allows to
provide a slice of `TimeRange` to provide time ranges for which the
query rule is active and disable it the rest of the time. `TimeRange`
objects holds two `From` and `Until` values of type `time.Time` to
configure the range.

**Demote (hide) product**
Hiding records from a response if a query rule is triggered thanks to
the newly introduced `Hide` field of `RuleConsequence`. This field can
be filled with a slice of `HiddenObject` that simply contain the object
IDs to discard from the response.

**Match the empty query**
The `Is` anchoring can now be used to match empty queries. As this was
not prevented previously and this change is happening on the engine
side, no change were made here.

**Disjunctive facet filters**
The `AutomaticFacetFilter` type has been added to let the user properly
fill values for `automaticFacetFilter` and
`automaticOptionalFacetFilters`. Tests were also added to check that
`automaticFacetFilter` and `automaticOptionalFacetFilters` can still be
passed as arrays of string, for backward-compatibility.

**Replace word**
Add the `Edit` type and its associated constructor functions
`DeleteEdit` and `ReplaceEdit`. Those are deprecating the use of the
`QueryIncrementalEdit` (Go comment has been updated to reflect the
deprecation).

**Reporting parameters**
The `explain` feature was introduced that let the user ask for
explanations (retrieved in the new `Explain` field from search
responses) from search queries (using the new `explain`  search
parameter with the following slice: `[]string{"params.rules"}`).
  • Loading branch information
aseure committed Sep 28, 2018
1 parent 47339c0 commit 9bf1d31
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 9 deletions.
1 change: 1 addition & 0 deletions algoliasearch/check_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Outer:
"attributesToSnippet",
"disableExactOnAttributes",
"disableTypoToleranceOnAttributes",
"explain",
"queryLanguages",
"responseFields":
if _, ok := v.([]string); !ok {
Expand Down
11 changes: 7 additions & 4 deletions algoliasearch/check_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,19 @@ func checkRules(rules []Rule) error {

case "query":
switch v.(type) {
case string, QueryIncrementalEdit:
case string, QueryIncrementalEdit, Map:
// OK
default:
return invalidType(k, "string or QueryIncrementalEdit")
return invalidType(k, "string or QueryIncrementalEdit or Map")
}

case "automaticFacetFilters",
"automaticOptionalFacetFilters":
if _, ok := v.([]string); !ok {
return invalidType(k, "[]string")
switch v.(type) {
case []string, []AutomaticFacetFilter:
// OK
default:
return invalidType(k, "[]string or []AutomaticFacetFilter")
}

default:
Expand Down
6 changes: 6 additions & 0 deletions algoliasearch/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,8 @@ func (i *index) SaveRuleWithRequestOptions(rule Rule, forwardToReplicas bool, op
return
}

rule.enableImplicitly()

params := Map{"forwardToReplicas": forwardToReplicas}
path := i.route + "/rules/" + rule.ObjectID + "?" + encodeMap(params)
err = i.client.request(&res, "PUT", path, rule, write, opts)
Expand All @@ -697,6 +699,10 @@ func (i *index) BatchRulesWithRequestOptions(rules []Rule, forwardToReplicas, cl
return
}

for i, _ := range rules {
rules[i].enableImplicitly()
}

params := Map{
"forwardToReplicas": forwardToReplicas,
"clearExistingRules": clearExistingRules,
Expand Down
1 change: 1 addition & 0 deletions algoliasearch/types_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type QueryRes struct {
AutomaticRadius string `json:"automaticRadius"`
ExhaustiveFacetsCount bool `json:"exhaustiveFacetsCount"`
ExhaustiveNbHits bool `json:"exhaustiveNbHits"`
Explain Map `json:"explain"`
Facets Map `json:"facets"`
FacetsStats Map `json:"facets_stats"`
Hits []Map `json:"hits"`
Expand Down
132 changes: 127 additions & 5 deletions algoliasearch/types_rule.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
package algoliasearch

import (
"encoding/json"
"fmt"
"time"
)

type Rule struct {
ObjectID string `json:"objectID,omitempty"`
Condition RuleCondition `json:"condition"`
Consequence RuleConsequence `json:"consequence"`
Description string `json:"description,omitempty"`
HighlightResult Map `json:"_highlightResult,omitempty"`
Condition RuleCondition `json:"condition"`
Consequence RuleConsequence `json:"consequence"`
Description string `json:"description,omitempty"`
Enabled bool `json:"enabled"` // Defaults to true
HighlightResult Map `json:"_highlightResult,omitempty"`
ObjectID string `json:"objectID,omitempty"`
Validity []TimeRange `json:"validity,omitempty"`
isExplicitlyDisabled bool
}

func (r *Rule) Enable() {
r.Enabled = true
}

func (r *Rule) Disable() {
r.Enabled = false
r.isExplicitlyDisabled = true
}

func (r *Rule) enableImplicitly() {
if !r.isExplicitlyDisabled {
r.Enable()
}
}

// RuleCondition is the part of an Algolia Rule which describes the condition
Expand Down Expand Up @@ -46,18 +70,83 @@ func NewRuleCondition(anchoring RulePatternAnchoring, pattern, context string) R
type RuleConsequence struct {
Params Map `json:"params,omitempty"`
Promote []PromotedObject `json:"promote,omitempty"`
Hide []HiddenObject `json:"hide,omitempty"`
UserData interface{} `json:"userData,omitempty"`
}

// AutomaticFacetFilter
type AutomaticFacetFilter struct {
Facet string `json:"facet"`
Disjunctive bool `json:"disjunctive"` // Defaults to false
Score int `json:"score"`
}

// QueryIncrementalEdit can be used as a value for the `query` key when used in
// the `RuleConsequence.Params` map. It is used to remove specific words from
// the original query string.
//
// Deprecated: Use `DeleteEdit` instead. More specifically, code previously
// written this way:
//
// consequence := algoliasearch.RuleConsquence{
// Params: algoliasearch.Map{
// "query": algoliasearch.QueryIncrementalEdit{
// Remove: []string{"term1", "term2"},
// },
// },
// }
//
// should now be written:
//
// consequence := algoliasearch.RuleConsequence{
// Params: algoliasearch.Map{
// "query": algoliasearch.Map{
// "edits": []algoliasearch.Edit{
// algoliasearch.DeleteEdit("term1"),
// algoliasearch.DeleteEdit("term2"),
// },
// },
// },
// }
//
type QueryIncrementalEdit struct {
Remove []string `json:"remove"`
}

type Edit struct {
Type string `json:"type"`
Delete string `json:"delete"`
Insert string `json:"insert,omitempty"`
}

// DeleteEdit returns a new `Edit` instance used to remove the given `word`
// from an original query when used as a `RuleConsequence.Params`.
func DeleteEdit(word string) Edit {
return Edit{
Type: "remove",
Delete: word,
}
}

// ReplaceEdit returns a new `Edit` instance used to replace the given `old`
// term with `new` in a query when used as a `RuleConsequence.Params`.
func ReplaceEdit(old, new string) Edit {
return Edit{
Type: "replace",
Delete: old,
Insert: new,
}
}

type PromotedObject struct {
ObjectID string `json:"objectID"`
Position int `json:"position"`
}

type HiddenObject struct {
ObjectID string `json:"objectID"`
}

type SaveRuleRes struct {
TaskID int `json:"taskID"`
UpdatedAt string `json:"updatedAt"`
Expand All @@ -78,6 +167,39 @@ type ClearRulesRes struct {
UpdatedAt string `json:"updatedAt"`
}

type TimeRange struct {
From time.Time
Until time.Time
}

type timeRangeResponse struct {
From int64 `json:"from"`
Until int64 `json:"until"`
}

func (r TimeRange) MarshalJSON() ([]byte, error) {
data := fmt.Sprintf(
`{"from":%d,"until":%d}`,
r.From.Unix(),
r.Until.Unix(),
)
return []byte(data), nil
}

func (r *TimeRange) UnmarshalJSON(b []byte) error {
var res timeRangeResponse

err := json.Unmarshal(b, &res)
if err != nil {
return fmt.Errorf("cannot unmarshal integer values of time range: %s", err)
}

r.From = time.Unix(res.From, 0)
r.Until = time.Unix(res.Until, 0)

return nil
}

type SearchRulesRes struct {
Hits []Rule `json:"hits"`
NbHits int `json:"nbHits"`
Expand Down

0 comments on commit 9bf1d31

Please sign in to comment.