{"action":"created","issue":{"url":"https://api.github.com/repos/sajari/sajari-sdk-react/issues/91","repository_url":"https://api.github.com/repos/sajari/sajari-sdk-react","labels_url":"https://api.github.com/repos/sajari/sajari-sdk-react/issues/91/labels{/name}","comments_url":"https://api.github.com/repos/sajari/sajari-sdk-react/issues/91/comments","events_url":"https://api.github.com/repos/sajari/sajari-sdk-react/issues/91/events","html_url":"https://github.com/sajari/sajari-sdk-react/issues/91","id":388426308,"node_id":"MDU6SXNzdWUzODg0MjYzMDg=","number":91,"title":"Add AggregateFilter controller","user":{"login":"benhinchley","id":7188324,"node_id":"MDQ6VXNlcjcxODgzMjQ=","avatar_url":"https://avatars1.githubusercontent.com/u/7188324?v=4","gravatar_id":"","url":"https://api.github.com/users/benhinchley","html_url":"https://github.com/benhinchley","followers_url":"https://api.github.com/users/benhinchley/followers","following_url":"https://api.github.com/users/benhinchley/following{/other_user}","gists_url":"https://api.github.com/users/benhinchley/gists{/gist_id}","starred_url":"https://api.github.com/users/benhinchley/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benhinchley/subscriptions","organizations_url":"https://api.github.com/users/benhinchley/orgs","repos_url":"https://api.github.com/users/benhinchley/repos","events_url":"https://api.github.com/users/benhinchley/events{/privacy}","received_events_url":"https://api.github.com/users/benhinchley/received_events","type":"User","site_admin":false},"labels":[{"id":409724745,"node_id":"MDU6TGFiZWw0MDk3MjQ3NDU=","url":"https://api.github.com/repos/sajari/sajari-sdk-react/labels/enhancement","name":"enhancement","color":"84b6eb","default":true}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2018-12-06T22:32:06Z","updated_at":"2018-12-18T11:40:36Z","closed_at":null,"author_association":"MEMBER","body":"We currently have the `Filter` class which acts as a convenience around creating and manipulating filters in the search interface. There is currently nothing that does the same for **Facets** (filters created from aggregates). Bellow is the source of a custom `CountAggegrateFilter` and `DateAggregateFilter` I built for a project, would be nice to have a series of these in the SDK for the different aggregate types (count, date) as well as a generic one.\r\n\r\n```js\r\n/*\r\n * CountAggregateFilter\r\n */\r\nexport class CountAggregateFilter extends Filter {\r\n  _field = \"\";\r\n  _counts = [];\r\n  _multi = false;\r\n\r\n  constructor(field, pipeline, multi = false, type = \"~\") {\r\n    super({}, [], multi);\r\n\r\n    this._field = field;\r\n    this._multi = multi;\r\n\r\n    pipeline.listen(responseUpdatedEvent, response => {\r\n      const current = this.get();\r\n      const aggregates = response.getAggregates();\r\n\r\n      this._counts = this._getCounts(aggregates);\r\n      this._clearFilterOptions(current);\r\n      this.setOptions(this._genFilterOptions(current, type));\r\n    });\r\n  }\r\n\r\n  _getCounts(aggregates) {\r\n    if (isEmpty(aggregates, this._field)) return [];\r\n\r\n    const counts = aggregates[`count.${this._field}`][\"count\"][\"counts\"];\r\n    return Object.keys(counts)\r\n      .map(key => {\r\n        return { name: key, count: counts[key] };\r\n      })\r\n      .filter(x => x.name !== \"\")\r\n      .sort(({ count: a }, { count: b }) => b - a);\r\n  }\r\n\r\n  _genFilterOptions(current, type = \"~\") {\r\n    return this._counts.reduce((opts, { name, count }) => {\r\n      if (!current.includes(name)) {\r\n        opts[name] = `${this._field}${type}\"${name}\"`;\r\n      }\r\n      return opts;\r\n    }, {});\r\n  }\r\n\r\n  _clearFilterOptions(current) {\r\n    const clear = Object.keys(this.getOptions()).reduce((opts, key) => {\r\n      if (!current.includes(key)) {\r\n        opts[key] = undefined;\r\n      }\r\n      return opts;\r\n    }, {});\r\n    this.setOptions(clear);\r\n  }\r\n}\r\n\r\nconst isEmpty = (aggregates, field) =>\r\n  !aggregates ||\r\n  !aggregates[`count.${field}`] ||\r\n  !aggregates[`count.${field}`][\"count\"] ||\r\n  !aggregates[`count.${field}`][\"count\"][\"counts\"];\r\n```\r\n\r\n```js\r\n/*\r\n * DateAggregateFilter\r\n */\r\nexport class DateAggregateFilter extends Filter {\r\n  _field = \"\";\r\n  _counts = [];\r\n  _multi = false;\r\n  _range = [0, 0];\r\n  _filter = \"\";\r\n  _current = \"\";\r\n\r\n  constructor(field, pipeline, multi = false, type = \"~\") {\r\n    super({}, [], multi);\r\n\r\n    this._field = field;\r\n    this._multi = multi;\r\n\r\n    pipeline.listen(responseUpdatedEvent, response => {\r\n      const aggregates = response.getAggregates();\r\n      if (isEmpty(aggregates, this._field)) return;\r\n\r\n      this._counts = this._getCounts(aggregates);\r\n      this._range = [\r\n        this._counts[0],\r\n        this._counts[this._counts.length - 1]\r\n      ].map(({ name }) => parseInt(name, 10));\r\n\r\n      this._emitOptionsUpdated();\r\n    });\r\n  }\r\n\r\n  range = () => this._range;\r\n  counts = () => this._counts;\r\n  filter = () => this._filter;\r\n\r\n  get = () => (this._current === \"\" ? [] : [this._current]);\r\n\r\n  set = (from, to) => {\r\n    const fromTimestamp = (new Date(`${from}.01.01`).getTime() / 1000).toFixed(\r\n      0\r\n    );\r\n    const toTimestamp = (new Date(`${to}.12.31`).getTime() / 1000).toFixed(0);\r\n\r\n    if (!isNaN(fromTimestamp) && !isNaN(toTimestamp)) {\r\n      this._filter = `(${this._field} >= ${fromTimestamp} AND ${\r\n        this._field\r\n      } <= ${toTimestamp})`;\r\n\r\n      this._current = `${from} - ${to}`;\r\n    }\r\n\r\n    this._emitSelectionUpdated();\r\n  };\r\n\r\n  clear = () => {\r\n    this._filter = \"\";\r\n    this._current = \"\";\r\n    this._emitSelectionUpdated();\r\n  };\r\n\r\n  _getCounts(aggregates) {\r\n    const counts = aggregates[`date.${this._field}`][\"date\"][\"dates\"];\r\n    return Object.keys(counts)\r\n      .map(key => {\r\n        return { name: key, count: counts[key] };\r\n      })\r\n      .filter(x => x.name !== \"\")\r\n      .sort(({ name: a }, { name: b }) => a - b);\r\n  }\r\n}\r\n\r\nconst isEmpty = (aggregates, field) =>\r\n  !aggregates ||\r\n  !aggregates[`date.${field}`] ||\r\n  !aggregates[`date.${field}`][\"date\"] ||\r\n  !aggregates[`date.${field}`][\"date\"][\"dates\"];\r\n```\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/sajari/sajari-sdk-react/issues/comments/448192365","html_url":"https://github.com/sajari/sajari-sdk-react/issues/91#issuecomment-448192365","issue_url":"https://api.github.com/repos/sajari/sajari-sdk-react/issues/91","id":448192365,"node_id":"MDEyOklzc3VlQ29tbWVudDQ0ODE5MjM2NQ==","user":{"login":"zlatanpham","id":12707960,"node_id":"MDQ6VXNlcjEyNzA3OTYw","avatar_url":"https://avatars0.githubusercontent.com/u/12707960?v=4","gravatar_id":"","url":"https://api.github.com/users/zlatanpham","html_url":"https://github.com/zlatanpham","followers_url":"https://api.github.com/users/zlatanpham/followers","following_url":"https://api.github.com/users/zlatanpham/following{/other_user}","gists_url":"https://api.github.com/users/zlatanpham/gists{/gist_id}","starred_url":"https://api.github.com/users/zlatanpham/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zlatanpham/subscriptions","organizations_url":"https://api.github.com/users/zlatanpham/orgs","repos_url":"https://api.github.com/users/zlatanpham/repos","events_url":"https://api.github.com/users/zlatanpham/events{/privacy}","received_events_url":"https://api.github.com/users/zlatanpham/received_events","type":"User","site_admin":false},"created_at":"2018-12-18T11:40:36Z","updated_at":"2018-12-18T11:40:36Z","author_association":"NONE","body":"@benhinchley, please help me review the [update](https://github.com/sajari/sajari-sdk-react/compare/master...zlatanpham:aggregate-filter-controller).   "}}
{"actor":{"display_login":"zlatanpham"}}