diff --git a/heimdall-frontend/package-lock.json b/heimdall-frontend/package-lock.json index 42a3ad06..0ee87077 100644 --- a/heimdall-frontend/package-lock.json +++ b/heimdall-frontend/package-lock.json @@ -4508,12 +4508,83 @@ }, "chownr": { "version": "1.0.1", + "asynckit": { + "version": "0.4.0", + "bundled": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "optional": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "requires": { + "hoek": "2.x.x" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "requires": { + "balanced-match": "^0.4.1", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "optional": true + }, + "co": { + "version": "4.6.0", "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "optional": true, + "requires": { + "delayed-stream": "~1.0.0" + } }, "concat-map": { "version": "0.0.1", @@ -4521,13 +4592,37 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", "bundled": true, "optional": true }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "optional": true, + "requires": { + "boom": "2.x.x" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, "debug": { "version": "2.6.9", "bundled": true, @@ -4541,6 +4636,11 @@ "bundled": true, "optional": true }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, "delegates": { "version": "1.0.0", "bundled": true, @@ -4548,6 +4648,30 @@ }, "detect-libc": { "version": "1.0.3", + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "forever-agent": { + "version": "0.6.1", "bundled": true, "optional": true }, @@ -4634,6 +4758,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "1.0.1" } @@ -4643,6 +4768,47 @@ "bundled": true, "optional": true }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "optional": true + }, "minimatch": { "version": "3.0.4", "bundled": true, @@ -4653,10 +4819,15 @@ "minimist": { "version": "0.0.8", "bundled": true + "mime-db": { + "version": "1.27.0", + "bundled": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, + "optional": true, "requires": { "safe-buffer": "5.1.1", "yallist": "3.0.2" @@ -4745,7 +4916,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4785,6 +4957,17 @@ }, "process-nextick-args": { "version": "2.0.0", + "version": "1.0.7", + "bundled": true, + "optional": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "optional": true + }, + "qs": { + "version": "6.4.0", "bundled": true, "optional": true }, @@ -4808,6 +4991,21 @@ }, "readable-stream": { "version": "2.3.6", + "version": "2.2.9", + "bundled": true, + "optional": true, + "requires": { + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" + } + }, + "request": { + "version": "2.81.0", "bundled": true, "optional": true, "requires": { @@ -4857,9 +5055,41 @@ "bundled": true, "optional": true }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "optional": true, + "requires": { + "hoek": "2.x.x" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "optional": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jodid25519": "^1.0.0", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -4888,6 +5118,40 @@ }, "tar": { "version": "4.4.1", + "version": "2.2.1", + "bundled": true, + "optional": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^2.2.0", + "fstream": "^1.0.10", + "fstream-ignore": "^1.0.5", + "once": "^1.3.3", + "readable-stream": "^2.1.4", + "rimraf": "^2.5.1", + "tar": "^2.2.1", + "uid-number": "^0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "optional": true, + "requires": { + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", "bundled": true, "optional": true, "requires": { @@ -4905,6 +5169,11 @@ "bundled": true, "optional": true }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, "wide-align": { "version": "1.1.2", "bundled": true, @@ -10404,6 +10673,148 @@ } } } + }, + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "neo-async": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", + "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==" + }, + "nested-property": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-0.0.7.tgz", + "integrity": "sha1-/yIvIzyoeTxoKLQRcJG+pZcTD08=" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node-forge": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", + "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + } + }, + "node-notifier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", + "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", + "requires": { + "growly": "^1.3.0", + "semver": "^5.4.1", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } }, "npm-run-path": { "version": "2.0.2", @@ -12027,9 +12438,9 @@ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { "version": "4.3.4", diff --git a/heimdall-frontend/src/actions/traces.js b/heimdall-frontend/src/actions/traces.js index ef2dfe9c..26a620a2 100644 --- a/heimdall-frontend/src/actions/traces.js +++ b/heimdall-frontend/src/actions/traces.js @@ -1,3 +1,4 @@ +import i18n from "../i18n/i18n" import {TraceConstants} from '../constants/actions-types' import {traceService} from '../services' @@ -19,6 +20,13 @@ export const getAllTraces = (query = {offset: 0, limit: 10}) => dispatch => { dispatch({type: TraceConstants.GET_TRACES, traces: data}) dispatch(finishLoading()) }) + .catch(error => { + console.log(error) + if (error.response && error.response.status === 400) { + dispatch(sendNotification({ type: 'error', message: i18n.t('error'), description: error.response.data.message })) + } + dispatch(finishLoading()) + }) } export const getTracer = tracerId => dispatch => { diff --git a/heimdall-frontend/src/components/traces/ListTraces.js b/heimdall-frontend/src/components/traces/ListTraces.js index 20b84bac..8bded980 100644 --- a/heimdall-frontend/src/components/traces/ListTraces.js +++ b/heimdall-frontend/src/components/traces/ListTraces.js @@ -21,7 +21,7 @@ class ListTraces extends Component { ( - {record.trace.resultStatus === 'ACTIVE' ? i18n.t('active') : i18n.t('inactive')} + {record.trace.resultStatus} )} /> diff --git a/heimdall-frontend/src/containers/Traces.js b/heimdall-frontend/src/containers/Traces.js index f2d02c9e..2f8f4618 100644 --- a/heimdall-frontend/src/containers/Traces.js +++ b/heimdall-frontend/src/containers/Traces.js @@ -4,12 +4,13 @@ import {connect} from 'react-redux' //components import {Card, Col, Form, notification, Row, Select, Input, Button, DatePicker, TimePicker} from 'antd' //actions +import i18n from "../i18n/i18n" import Loading from '../components/ui/Loading' import PageHeader from '../components/ui/PageHeader' import ListTraces from '../components/traces/ListTraces' import FilterTraceUtils from "../utils/FilterTraceUtils" import {getAllTraces, initLoading} from "../actions/traces" -import i18n from "../i18n/i18n"; +import moment from 'moment' const {Option} = Select @@ -22,12 +23,27 @@ class Traces extends Component { searchQuery: {}, filters: FilterTraceUtils.getFilters(), filtersSelected: [], - filterOrder: 0 + filterOrder: 0, + search: false } componentDidMount() { - this.props.dispatch(initLoading()) - this.props.dispatch(getAllTraces({offset: 0, limit: 10, filtersSelected: this.state.filtersSelected})) + const urlSearch = this.props.history.location.search.replace('?', '') + const filters = FilterTraceUtils.URLSearchToFilters(urlSearch) + if (filters) { + const filtersComplete = FilterTraceUtils.completeFilters(filters) + this.setState({ ...this.state, filtersSelected: filtersComplete, search: true }) + } else { + this.setState({ ...this.state, filtersSelected: [], search: true }) + } + } + + componentWillUpdate(nextProps, nextState) { + if (nextState.search) { + this.props.dispatch(initLoading()) + this.props.dispatch(getAllTraces({offset: 0, limit: 10, filtersSelected: nextState.filtersSelected })) + this.setState({ ...nextState, search: false }) + } } componentWillReceiveProps(newProps) { @@ -44,8 +60,9 @@ class Traces extends Component { } sendFilters = () => { - this.props.dispatch(initLoading()) - this.props.dispatch(getAllTraces({offset: 0, limit: 10, filtersSelected: this.state.filtersSelected})) + const { filtersSelected } = this.state + const urlSearch = FilterTraceUtils.filtersToURLSearch(FilterTraceUtils.reduceFilterToURL(filtersSelected)) + this.props.history.push(`?${urlSearch}`) } updateFiltersSelected = (element) => { @@ -57,7 +74,7 @@ class Traces extends Component { const newFiltersSelected = filtersSelected.filter((e) => e.name !== element.name) newFiltersSelected.push(element) newFiltersSelected.sort(this.orderFiltersSelected) - this.setState({...this.state, filtersSelected: newFiltersSelected, filterOrder: orderFilter}) + this.setState({...this.state, filtersSelected: newFiltersSelected, filterOrder: orderFilter, search: false }) } removeFromFiltersSelected = (element) => { @@ -153,7 +170,6 @@ class Traces extends Component { const options = element.operations.map((operation) => { return }) - return ( @@ -176,6 +192,7 @@ class Traces extends Component { { element.type === "date" && 0 && moment(element.firstValue, formatDate)} showTime={timeInput} format={formatDate} onChange={this.handleChangeValueFilter(element)} style={{width: "100%"}}/> @@ -206,6 +223,7 @@ class Traces extends Component { { element.type === "date" ? 0 && moment(element.secondValue, formatDate)} showTime={timeInput} format={formatDate} onChange={this.handleChangeValue2Filter(element)} style={{width: "100%"}}/> diff --git a/heimdall-frontend/src/services/TraceService.js b/heimdall-frontend/src/services/TraceService.js index 8cb5d8fa..e7f85a4b 100644 --- a/heimdall-frontend/src/services/TraceService.js +++ b/heimdall-frontend/src/services/TraceService.js @@ -1,6 +1,6 @@ import i18n from "../i18n/i18n" import {HTTPv1} from "../utils/Http" -import {EnumFilters} from "../utils/EnumFiltersUtils" +import FilterTraceUtils from "../utils/FilterTraceUtils" const getTrace = (traceId) => { if (!traceId) { @@ -23,7 +23,7 @@ const getTrace = (traceId) => { const getTraces = (params = {params: {}}) => { const offset = params.params.offset const limit = params.params.limit - const filtersSelected = updateOperationSelectedToEnum(params.params.filtersSelected) + const filtersSelected = FilterTraceUtils.updateOperationSelectedToEnum(params.params.filtersSelected) return HTTPv1.post(`/traces?offset=${offset}&limit=${limit}`, filtersSelected) .then(res => { return Promise.resolve(res.data) @@ -37,24 +37,6 @@ const getTraces = (params = {params: {}}) => { }) } -const updateOperationSelectedToEnum = (filters) => { - - let filtersToSend = []; - - filters.forEach((f) => { - let filter = {}; - filter['operationSelected'] = EnumFilters[f.operationSelected] - filter['firstValue'] = f.firstValue - filter['secondValue'] = f.secondValue - filter['name'] = f.name - filter['type'] = f.type - - filtersToSend.push(filter) - }) - - return filtersToSend; -} - export const traceService = { getTrace, getTraces diff --git a/heimdall-frontend/src/utils/FilterTraceUtils.js b/heimdall-frontend/src/utils/FilterTraceUtils.js index 453ab37d..f4c037b7 100644 --- a/heimdall-frontend/src/utils/FilterTraceUtils.js +++ b/heimdall-frontend/src/utils/FilterTraceUtils.js @@ -1,3 +1,24 @@ +import qs from 'qs' +import {EnumFilters} from "./EnumFiltersUtils" + +const completeFilters = filters => { + + if (filters) { + const filtersResult = JSON.parse(JSON.stringify(filters)) + + filtersResult.forEach(filter => { + const filterEqual = getFilters().find(f => f.name === filter.name) + filter.operations = filterEqual.operations + filter.possibleValues = filterEqual.possibleValues + filter.label = filterEqual.label + }) + + return filtersResult + } + + return filters +} + const getFilters = () => { return [ { @@ -202,8 +223,6 @@ const getOperations = (type) => { ] case "date": return [ - "equals", - "not equals", "between", "less than", "greater than", @@ -216,6 +235,7 @@ const getOperations = (type) => { "this month", "last month", "this year", + "not equals", "none", "all" ] @@ -248,8 +268,80 @@ const getPossibleValues = (field) => { } } +const filtersToURLSearch = filters => { + filters.forEach(filter => { + if (filter.type === 'date') { + filter.firstValue = (new Date(filter.firstValue).getTime() / 1000).toFixed(0) + if (filter.operationSelected === 'between') { + filter.secondValue = (new Date(filter.secondValue).getTime() / 1000).toFixed(0) + } + } + }) + return qs.stringify(filters) +} + +const URLSearchToFilters = urlSearch => { + const filtersObject = qs.parse(urlSearch) + const result = Object.keys(filtersObject).map(key => filtersObject[key]) + + result.forEach(filter => { + if (filter.type === 'date') { + filter.firstValue = new Date(filter.firstValue * 1000).toISOString() + if (filter.operationSelected === 'between') { + filter.secondValue = new Date(filter.secondValue * 1000).toISOString() + } + } + }) + + return result +} + +const updateOperationSelectedToEnum = (filters) => { + + let filtersToSend = []; + + if (filters) { + filters.forEach((f) => { + let filter = {}; + filter['operationSelected'] = EnumFilters[f.operationSelected] + filter['firstValue'] = f.firstValue + filter['secondValue'] = f.secondValue + filter['name'] = f.name + filter['type'] = f.type + + filtersToSend.push(filter) + }) + } + + return filtersToSend; +} + +const reduceFilterToURL = filters => { + let filtersToSend = []; + + if (filters) { + filters.forEach((f) => { + let filter = {}; + filter['operationSelected'] = f.operationSelected + filter['firstValue'] = f.firstValue + filter['secondValue'] = f.secondValue + filter['name'] = f.name + filter['type'] = f.type + + filtersToSend.push(filter) + }) + } + + return filtersToSend; +} + export default { getFilters, getOperations, - getPossibleValues + getPossibleValues, + filtersToURLSearch, + URLSearchToFilters, + updateOperationSelectedToEnum, + completeFilters, + reduceFilterToURL } \ No newline at end of file