From 1bcf31f5db69c4e91f040bbb2a8013e4de4fa0a5 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Tue, 25 Jun 2024 14:44:03 +0530 Subject: [PATCH 01/21] supporting filter functions and aggerations are added --- src/datasources/solr9x.js | 1229 +++++++++++++++++++++++++++++++++++++ src/edges.js | 20 +- 2 files changed, 1243 insertions(+), 6 deletions(-) create mode 100644 src/datasources/solr9x.js diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js new file mode 100644 index 0000000..e9545b7 --- /dev/null +++ b/src/datasources/solr9x.js @@ -0,0 +1,1229 @@ +if (!window.hasOwnProperty("es")) { es = {}} + +// request method to be used throughout. Set this before using the module if you want it different +es.requestMethod = "get"; + +// add request headers (such as Auth) if you need to +es.requestHeaders = false; + +// Base classes +es.Aggregation = class { + static type = "aggregation"; + + constructor(params) { + this.name = params.name; + this.aggs = params.aggs || []; + } + + addAggregation(agg) { + for (var i = 0; i < this.aggs.length; i++) { + if (this.aggs[i].name === agg.name) { + return; + } + } + this.aggs.push(agg); + } + + removeAggregation() {} + clearAggregations() {} + + // for use by sub-classes, for their convenience in rendering + // the overall structure of the aggregation to an object + _make_aggregation(type, body) { + var obj = {}; + obj[this.name] = {}; + obj[this.name][type] = body; + + if (this.aggs.length > 0) { + obj[this.name]["aggs"] = {}; + for (var i = 0; i < this.aggs.length; i++) { + $.extend(obj[this.name]["aggs"], this.aggs[i].objectify()); + } + } + + return obj; + } + + _parse_wrapper(obj, type) { + this.name = Object.keys(obj)[0]; + var body = obj[this.name][type]; + + var aggs = obj[this.name].aggs ? obj[this.name].aggs : obj[this.name].aggregations; + if (aggs) { + var anames = Object.keys(aggs); + for (var i = 0; i < anames.length; i++) { + var name = anames[i]; + var agg = aggs[anames[i]]; + var subtype = Object.keys(agg)[0]; + var raw = {}; + raw[name] = agg; + var oa = es.aggregationFactory(subtype, {raw: raw}); + if (oa) { + this.addAggregation(oa); + } + } + } + + return body; + } +}; + + +es.Filter = class { + static type = "filter"; + + constructor(params) { + this.field = params.field; + } + + matches(other) { + return this._baseMatch(other); + } + + _baseMatch(other) { + // type must match + if (other.type !== this.type) { + return false; + } + // field (if set) must match + if (other.field && other.field !== this.field) { + return false; + } + // otherwise this matches + return true; + } + + objectify() {} + parse() {} +}; + +es.Query = class { + constructor(params) { + if (!params) { params = {}} + // properties that can be set directly + this.filtered = false; // this is no longer present in es5.x+ + this.trackTotalHits = true; // FIXME: hard code this for the moment, we can introduce the ability to vary it later + + this.size = this.getParam(params.size, false); + this.from = this.getParam(params.from, false); + this.fields = this.getParam(params.fields, []); + this.aggs = this.getParam(params.aggs, []); + this.must = this.getParam(params.must, []); + this.mustNot = this.getParam(params.mustNot, []); + this.should = this.getParam(params.should, []); + this.minimumShouldMatch = this.getParam(params.minimumShouldMatch, false); + + // defaults from properties that will be set through their setters + this.queryString = false; + this.sort = []; + + // ones that we haven't used yet, so are awaiting implementation + // NOTE: once we implement these, they also need to be considered in merge() + this.source = this.getParam(params.source, false); + this.partialFields = this.getParam(params.partialField, false); + this.scriptFields = this.getParam(params.scriptFields, false); + this.partialFields = this.getParam(params.partialFields, false); + this.scriptFields = this.getParam(params.scriptFields, false); + + // for old versions of ES, so are not necessarily going to be implemented + this.facets = this.getParam(params.facets, []); + + /////////////////////////////////////////////////////////// + // final part of construction - set the dynamic properties + // via their setters + + if (params.queryString) { + this.setQueryString(params.queryString); + } + + if (params.sort) { + this.setSortBy(params.sort); + } + + // finally, if we're given a raw query, parse it + if (params.raw) { + this.parse(params.raw) + } + } + + getParam(param, defaultValue) { + return param !== undefined ? param : defaultValue; + } + + getSize() { + if (this.size !== undefined && this.size !== false) { + return this.size; + } + return 10; + } + + getFrom() { + if (this.from) { + return this.from + } + return 0; + } + + addField(field) { + if (this.fields.indexOf(field) === -1) { + this.fields.push(field); + } + } + + setQueryString(params) { + var qs = params; + if (!(params instanceof es.QueryString)) { + if (typeof params === 'object') { + qs = new es.QueryString(params); + } else { + qs = new es.QueryString({queryString: params}); + } + } + this.queryString = qs; + } + + getQueryString() { + return this.queryString; + } + + removeQueryString() { + this.queryString = false; + } + + setSortBy(params) { + // overwrite anything that was there before + this.sort = []; + // ensure we have a list of sort options + var sorts = params; + if (!Array.isArray(params)) { + sorts = [params] + } + // add each one + for (var i = 0; i < sorts.length; i++) { + this.addSortBy(sorts[i]); + } + } + + addSortBy(params) { + // ensure we have an instance of es.Sort + var sort = params; + if (!(params instanceof es.Sort)) { + sort = new es.Sort(params); + } + // prevent repeated sort options being added + for (var i = 0; i < this.sort.length; i++) { + var so = this.sort[i]; + if (so.field === sort.field) { + return; + } + } + // add the sort option + this.sort.push(sort); + } + + prependSortBy(params) { + // ensure we have an instance of es.Sort + var sort = params; + if (!(params instanceof es.Sort)) { + sort = new es.Sort(params); + } + this.removeSortBy(sort); + this.sort.unshift(sort); + } + + removeSortBy(params) { + // ensure we have an instance of es.Sort + var sort = params; + if (!(params instanceof es.Sort)) { + sort = new es.Sort(params); + } + var removes = []; + for (var i = 0; i < this.sort.length; i++) { + var so = this.sort[i]; + if (so.field === sort.field) { + removes.push(i); + } + } + removes = removes.sort().reverse(); + for (var i = 0; i < removes.length; i++) { + this.sort.splice(removes[i], 1); + } + } + + getSortBy() { + return this.sort; + } + + setSourceFilters(params) { + if (!this.source) { + this.source = {include: [], exclude: []}; + } + if (params.include) { + this.source.include = params.include; + } + if (params.exclude) { + this.source.exclude = params.exclude; + } + } + + addSourceFilters(params) { + if (!this.source) { + this.source = {include: [], exclude: []}; + } + if (params.include) { + if (this.source.include) { + Array.prototype.push.apply(this.source.include, params.include); + } else { + this.source.include = params.include; + } + } + if (params.exclude) { + if (this.source.include) { + Array.prototype.push.apply(this.source.include, params.include); + } else { + this.source.include = params.include; + } + } + } + + getSourceIncludes() { + if (!this.source) { + return []; + } + return this.source.include; + } + + getSourceExcludes() { + if (!this.source) { + return []; + } + return this.source.exclude; + }; + + getAggregation(params) { + var name = params.name; + for (var i = 0; i < this.aggs.length; i++) { + var a = this.aggs[i]; + if (a.name === name) { + return a; + } + } + } + + addAggregation(agg, overwrite) { + if (overwrite) { + this.removeAggregation(agg.name); + } else { + for (var i = 0; i < this.aggs.length; i++) { + if (this.aggs[i].name === agg.name) { + return; + } + } + } + this.aggs.push(agg); + } + + removeAggregation(name) { + var removes = []; + for (var i = 0; i < this.aggs.length; i++) { + if (this.aggs[i].name === name) { + removes.push(i); + } + } + removes = removes.sort().reverse(); + for (var i = 0; i < removes.length; i++) { + this.aggs.splice(removes[i], 1); + } + } + + clearAggregations() { + this.aggs = []; + } + + listAggregations() { + return this.aggs; + } + + addMust(filter) { + var existing = this.listMust(filter); + if (existing.length === 0) { + this.must.push(filter); + } + } + + listMust(template) { + return this.listFilters({boolType: "must", template: template}); + } + + removeMust(template) { + var removes = []; + for (var i = 0; i < this.must.length; i++) { + var m = this.must[i]; + if (m.matches(template)) { + removes.push(i); + } + } + removes = removes.sort().reverse(); + for (var i = 0; i < removes.length; i++) { + this.must.splice(removes[i], 1); + } + // return the count of filters that were removed + return removes.length; + } + + clearMust() { + this.must = []; + } + + addMustNot(filter) { + var existing = this.listMustNot(filter); + if (existing.length === 0) { + this.mustNot.push(filter); + } + } + + listMustNot(template) { + return this.listFilters({boolType: "must_not", template: template}); + } + + removeMustNot(template) { + var removes = []; + for (var i = 0; i < this.mustNot.length; i++) { + var m = this.mustNot[i]; + if (m.matches(template)) { + removes.push(i); + } + } + removes = removes.sort().reverse(); + for (var i = 0; i < removes.length; i++) { + this.mustNot.splice(removes[i], 1); + } + // return the count of filters that were removed + return removes.length; + } + + clearMustNot() { + this.mustNot = []; + } + + addShould(filter) { + var existing = this.listShould(filter); + if (existing.length === 0) { + this.should.push(filter); + } + } + + listShould(template) { + return this.listFilters({boolType: "should", template: template}); + } + + removeShould(template) { + var removes = []; + for (var i = 0; i < this.should.length; i++) { + var m = this.should[i]; + if (m.matches(template)) { + removes.push(i); + } + } + removes = removes.sort().reverse(); + for (var i = 0; i < removes.length; i++) { + this.should.splice(removes[i], 1); + } + // return the count of filters that were removed + return removes.length; + } + + clearShould() { + this.should = []; + } + + setMinimumShouldMatch(val) { + this.minimumShouldMatch = val; + } + + getMinimumShouldMatch() { + return this.minimumShouldMatch; + } + + listFilters(params) { + var boolType = params.boolType; + var template = params.template; + var filters = []; + var source; + if (boolType === "must") { + source = this.must; + } else if (boolType === "must_not") { + source = this.mustNot; + } else if (boolType === "should") { + source = this.should; + } + for (var i = 0; i < source.length; i++) { + var f = source[i]; + if (f.matches(template)) { + filters.push(f); + } + } + return filters; + } + + getFilters() { + var filters = []; + for (var i = 0; i < this.must.length; i++) { + filters.push(this.must[i]); + } + for (var i = 0; i < this.mustNot.length; i++) { + filters.push(this.mustNot[i]); + } + for (var i = 0; i < this.should.length; i++) { + filters.push(this.should[i]); + } + return filters; + } + + hasFilters() { + if (this.must.length > 0 || this.mustNot.length > 0 || this.should.length > 0) { + return true; + } + return false; + } + + parse(raw) { + var parsed = JSON.parse(raw); + // re-initialise + this.must = []; + this.mustNot = []; + this.should = []; + this.minimumShouldMatch = false; + // and then apply + this.apply(parsed); + } + + apply(parsed) { + if (parsed.size !== undefined) { + this.size = parsed.size; + } + if (parsed.from !== undefined) { + this.from = parsed.from; + } + if (parsed.track_total_hits !== undefined) { + this.trackTotalHits = parsed.track_total_hits; + } + if (parsed._source) { + this.setSourceFilters(parsed._source); + } + if (parsed.fields) { + this.fields = parsed.fields; + } + if (parsed.sort) { + this.setSortBy(parsed.sort); + } + if (parsed.aggs) { + for (var a in parsed.aggs) { + this.addAggregation(new es.Aggregation({name: a, raw: parsed.aggs[a]})); + } + } + if (parsed.query) { + if (parsed.query.query_string) { + this.setQueryString(parsed.query.query_string); + } + if (parsed.query.bool) { + if (parsed.query.bool.must) { + for (var i = 0; i < parsed.query.bool.must.length; i++) { + this.addMust(new es.Filter({raw: parsed.query.bool.must[i]})); + } + } + if (parsed.query.bool.must_not) { + for (var i = 0; i < parsed.query.bool.must_not.length; i++) { + this.addMustNot(new es.Filter({raw: parsed.query.bool.must_not[i]})); + } + } + if (parsed.query.bool.should) { + for (var i = 0; i < parsed.query.bool.should.length; i++) { + this.addShould(new es.Filter({raw: parsed.query.bool.should[i]})); + } + } + if (parsed.query.bool.minimum_should_match) { + this.setMinimumShouldMatch(parsed.query.bool.minimum_should_match); + } + } + } + } + + export() { + var exported = { + // see https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html + size: this.size || 10, + from: this.from || 0, + track_total_hits: this.trackTotalHits, + query: {}, + aggs: {}, + _source: {}, + sort: [] + }; + if (this.getSourceIncludes().length > 0 || this.getSourceExcludes().length > 0) { + exported._source = { + includes: this.getSourceIncludes(), + excludes: this.getSourceExcludes() + }; + } + if (this.fields.length > 0) { + exported.fields = this.fields; + } + if (this.sort.length > 0) { + for (var i = 0; i < this.sort.length; i++) { + exported.sort.push(this.sort[i].export()); + } + } else { + delete exported.sort; + } + var filters = this.getFilters(); + if (filters.length > 0) { + exported.query.bool = {}; + if (this.must.length > 0) { + exported.query.bool.must = []; + for (var i = 0; i < this.must.length; i++) { + exported.query.bool.must.push(this.must[i].export()); + } + } + if (this.mustNot.length > 0) { + exported.query.bool.must_not = []; + for (var i = 0; i < this.mustNot.length; i++) { + exported.query.bool.must_not.push(this.mustNot[i].export()); + } + } + if (this.should.length > 0) { + exported.query.bool.should = []; + for (var i = 0; i < this.should.length; i++) { + exported.query.bool.should.push(this.should[i].export()); + } + } + if (this.minimumShouldMatch) { + exported.query.bool.minimum_should_match = this.minimumShouldMatch; + } + } else if (this.queryString) { + exported.query = { + query_string: this.queryString.export() + }; + } else { + exported.query = { + match_all: {} + }; + } + for (var i = 0; i < this.aggs.length; i++) { + var agg = this.aggs[i]; + var inner = {}; + inner[agg.type] = {}; + if (agg.field) { + inner[agg.type].field = agg.field; + } + if (agg.script) { + inner[agg.type].script = agg.script; + } + if (agg.size) { + inner[agg.type].size = agg.size; + } + if (agg.interval) { + inner[agg.type].interval = agg.interval; + } + exported.aggs[agg.name] = inner; + } + // if we haven't added any aggs, remove the aggs key + if (Object.keys(exported.aggs).length === 0) { + delete exported.aggs; + } + // remove the _source key if it wasn't populated + if (Object.keys(exported._source).length === 0) { + delete exported._source; + } + return JSON.stringify(exported, null, 4); + } + + merge(q) { + // size / limit + if (q.size) { + this.size = q.size; + } + // from / offset + if (q.from) { + this.from = q.from; + } + // fields + for (var i = 0; i < q.fields.length; i++) { + this.addField(q.fields[i]); + } + // aggregations + for (var i = 0; i < q.aggs.length; i++) { + this.addAggregation(q.aggs[i]); + } + // musts + for (var i = 0; i < q.must.length; i++) { + this.addMust(q.must[i]); + } + // must nots + for (var i = 0; i < q.mustNot.length; i++) { + this.addMustNot(q.mustNot[i]); + } + // shoulds + for (var i = 0; i < q.should.length; i++) { + this.addShould(q.should[i]); + } + if (q.minimumShouldMatch) { + this.setMinimumShouldMatch(q.minimumShouldMatch); + } + // sort + for (var i = 0; i < q.sort.length; i++) { + this.addSortBy(q.sort[i]); + } + // query string + if (q.queryString) { + this.setQueryString(q.queryString); + } + } +}; + +es.QueryString = class { + constructor(params) { + this.queryString = params.queryString || false; + this.defaultField = params.defaultField || false; + this.defaultOperator = params.defaultOperator || "OR"; + + this.fuzzify = params.fuzzify || false; // * or ~ + this.escapeSet = params.escapeSet || es.specialCharsSubSet; + this.pairs = params.pairs || es.characterPairs; + this.unEscapeSet = params.unEscapeSet || es.specialChars; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var qs = this._escape(this._fuzzify(this.queryString)); + var obj = {q: qs}; + if (this.defaultOperator) { + obj["q.op"] = this.defaultOperator; + } + if (this.defaultField) { + obj["df"] = this.defaultField; + } + return obj; + } + + parse(obj) { + if (obj.q) { + this.queryString = this._unescape(obj.q); + } + if (obj["q.op"]) { + this.defaultOperator = obj["q.op"]; + } + if (obj.df) { + this.defaultField = obj.df; + } + } + + _fuzzify(str) { + if (!this.fuzzify || !(this.fuzzify === "*" || this.fuzzify === "~")) { + return str; + } + + if (!(str.indexOf('*') === -1 && str.indexOf('~') === -1 && str.indexOf(':') === -1)) { + return str; + } + + var pq = ""; + var optparts = str.split(' '); + for (var i = 0; i < optparts.length; i++) { + var oip = optparts[i]; + if (oip.length > 0) { + oip = oip + this.fuzzify; + this.fuzzify === "*" ? oip = "*" + oip : false; + pq += oip + " "; + } + } + return pq; + }; + + _escapeRegExp(string) { + return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); + }; + + _replaceAll(string, find, replace) { + return string.replace(new RegExp(this._escapeRegExp(find), 'g'), replace); + }; + + _unReplaceAll(string, find) { + return string.replace(new RegExp("\\\\(" + this._escapeRegExp(find) + ")", 'g'), "$1"); + }; + + _paired(string, pair) { + var matches = (string.match(new RegExp(this._escapeRegExp(pair), "g"))) || []; + return matches.length % 2 === 0; + }; + + _escape(str) { + // make a copy of the special characters (we may modify it in a moment) + var scs = this.escapeSet.slice(0); + + // first check for pairs, and push any extra characters to be escaped + for (var i = 0; i < this.pairs.length; i++) { + var char = this.pairs[i]; + if (!this._paired(str, char)) { + scs.push(char); + } + } + + // now do the escape + for (var i = 0; i < scs.length; i++) { + var char = scs[i]; + str = this._replaceAll(str, char, "\\" + char); + } + + return str; + }; + + _unescape(str) { + for (var i = 0; i < this.unEscapeSet.length; i++) { + var char = this.unEscapeSet[i]; + str = this._unReplaceAll(str, char) + } + return str; + }; +} + + + +// Factories +es.aggregationFactory = function(type, params) { + for (const [key, value] of Object.entries(es)) { + if (es._classExtends(es[key], es.Aggregation)) { + if (es[key].type === type) { + // Convert Elasticsearch specific parameters to es if needed + if (type === "terms") { + params.field = params.field || false; + params.size = params.size || 10; // Use 'rows' for Solr, mapped from 'size' + params.orderBy = params.orderBy || "_count"; + if (params.orderBy[0] !== "_") { + params.orderBy = "_" + params.orderBy; + } + params.orderDir = params.orderDir || "desc"; + } + return new es[key](params); + } + } + } + throw new Error(`Unknown aggregation type: ${type}`); +}; + +es.filterFactory = function(type, params) { + // query string is a special case + if (type === "query_string") { + return new es.QueryString(params); + } + + // otherwise auto-detect + for (const [key, value] of Object.entries(es)) { + if (es._classExtends(es[key], es.Filter)) { + if (es[key].type === type) { + // Convert Elasticsearch specific parameters to Solr if needed + if (type === "terms") { + params.field = params.field || false; + params.values = params.values || []; + params.execution = params.execution || false; + } + return new es[key](params); + } + } + } + throw new Error(`Unknown filter type: ${type}`); +}; + +// Filter extended classes starts here +es.TermFilter = class extends es.Filter { + static type = "term"; + + constructor(params) { + super(params); + // this.filter handled by superclass + this.value = params.value || false; + + if (params.raw) { + this.parse(params.raw); + } + } + + matches(other) { + // ask the parent object first + let pm = this._baseMatch(other); + if (!pm) { + return false; + } + // value (if set) must match + if (other.value && other.value !== this.value) { + return false; + } + + return true; + } + + objectify() { + // Solr-specific object structure + var obj = {}; + obj[this.field] = this.value; + return obj; + } + + parse(obj) { + // Solr-specific parsing + this.field = Object.keys(obj)[0]; + this.value = obj[this.field]; + } +} + +es.TermsFilter = class extends es.Filter { + static type = "terms"; + + constructor(params) { + super(params); + // this.field handled by superclass + this.values = params.values || false; + this.execution = params.execution || false; + + if (params.raw) { + this.parse(params.raw); + } + } + + matches(other) { + // ask the parent object first + let pm = this._baseMatch(other); + if (!pm) { + return false; + } + + // values (if set) must be the same list + if (other.values) { + if (other.values.length !== this.values.length) { + return false; + } + for (var i = 0; i < other.values.length; i++) { + if ($.inArray(other.values[i], this.values) === -1) { + return false; + } + } + } + + return true; + } + + objectify() { + var val = this.values || []; + var filterQuery = val.map(value => `${this.field}:${value}`).join(' OR '); + return { fq: filterQuery }; + } + + parse(obj) { + if (obj.fq) { + let terms = obj.fq.split(' OR '); + let field = terms[0].split(':')[0]; + let values = terms.map(term => term.split(':')[1]); + + this.field = field; + this.values = values; + } + } + + add_term(term) { + if (!this.values) { + this.values = []; + } + if ($.inArray(term, this.values) === -1) { + this.values.push(term); + } + } + + has_term(term) { + if (!this.values) { + return false; + } + return $.inArray(term, this.values) >= 0; + } + + remove_term(term) { + if (!this.values) { + return; + } + var idx = $.inArray(term, this.values); + if (idx >= 0) { + this.values.splice(idx, 1); + } + } + + has_terms() { + return (this.values !== false && this.values.length > 0); + } + + term_count() { + return this.values === false ? 0 : this.values.length; + } + + clear_terms() { + this.values = false; + } +}; + + +// Aggregation extended classes starts here +es.TermsAggregation = class extends es.Aggregation { + static type = "terms"; + + constructor(params) { + super(params); + this.field = params.field || false; + this.size = params.size || 10; // 'size' in Elasticsearch, will convert to 'rows' for Solr + + // set the ordering for the first time + this.orderBy = "_count"; + if (params.orderBy) { + this.orderBy = params.orderBy; + if (this.orderBy[0] !== "_") { + this.orderBy = "_" + this.orderBy; + } + } + this.orderDir = params.orderDir || "desc"; + + if (params.raw) { + this.parse(params.raw); + } + } + + // provide a method to set and normalize the ordering in future + setOrdering(orderBy, orderDir) { + this.orderBy = orderBy; + if (this.orderBy[0] !== "_") { + this.orderBy = "_" + this.orderBy; + } + this.orderDir = orderDir; + } + + objectify() { + // Solr facets configuration + const body = { + field: this.field, + rows: this.size, // Convert 'size' to 'rows' for Solr + order: {} + }; + + // Translate Elasticsearch orderBy to Solr sort + let solrSort = ""; + if (this.orderBy === "_count") { + solrSort = "count"; + } else if (this.orderBy === "_term") { + solrSort = "index"; + } + body.order[solrSort] = this.orderDir; + + return this._make_aggregation(es.TermsAggregation.type, body); + } + + parse(obj) { + const body = this._parse_wrapper(obj, es.TermsAggregation.type); + this.field = body.field; + if (body.rows) { + this.size = body.rows; // Convert 'rows' to 'size' + } + if (body.order) { + const solrSort = Object.keys(body.order)[0]; + this.orderDir = body.order[solrSort]; + + // Translate Solr sort back to Elasticsearch orderBy + if (solrSort === "count") { + this.orderBy = "_count"; + } else if (solrSort === "index") { + this.orderBy = "_term"; + } + } + } +}; + + + +es.doQuery = (params) => { + const { success, error, complete, search_url, query, datatype } = params; + + const solrArgs = this._es2solr({ query : query }); + + const searchUrl = search_url; + // Generate the Solr query URL + const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); + + var error_callback = es.queryError(error); + var success_callback = es.querySuccess(success, error_callback); + + // Execution of solr query + $.get({ + url: fullUrl, + datatype: datatype ? datatype : "jsonp", + success: success_callback, + error: error_callback, + jsonp: 'json.wrf' + }); + +}; + +es.querySuccess = function (callback, error_callback) { + return function(data) { + if (data.hasOwnProperty("error")) { + error_callback(data); + return; + } + + var result = new es.Result({raw: data}); + callback(result); + } +}; + +es.queryError = function (callback) { + return function(data) { + if (callback) { + callback(data); + } else { + throw new Error(data); + } + } +}; + +es.Result = class { + constructor(params) { + this.data = JSON.parse(params.raw); + } + + buckets(facet_name) { + if (this.data.facet_counts) { + if (this.data.facet_counts.facet_fields && this.data.facet_counts.facet_fields[facet_name]) { + return this._convertFacetToBuckets(this.data.facet_counts.facet_fields[facet_name]); + } else if (this.data.facet_counts.facet_queries && this.data.facet_counts.facet_queries[facet_name]) { + return this._convertFacetToBuckets(this.data.facet_counts.facet_queries[facet_name]); + } + } + return []; + } + + _convertFacetToBuckets(facet) { + let buckets = []; + for (let i = 0; i < facet.length; i += 2) { + buckets.push({ + key: facet[i], + doc_count: facet[i + 1] + }); + } + return buckets; + } + + aggregation(facet_name) { + return { + buckets: this.buckets(facet_name) + }; + } + + results() { + var res = []; + if (this.data.response && this.data.response.docs) { + for (var i = 0; i < this.data.response.docs.length; i++) { + res.push(this.data.response.docs[i]); + } + } + return res; + } + + total() { + if (this.data.response && this.data.response.numFound !== undefined) { + return parseInt(this.data.response.numFound); + } + return false; + } +}; + + +// Helper functions +// Method to convert es query to Solr query +function _es2solr({ query }) { + const solrQuery = {}; + let solrFacets = [] + + // Handle the query part + if (query.query) { + const queryPart = query.query; + if (queryPart.match) { + const field = Object.keys(queryPart.match)[0]; + const value = queryPart.match[field]; + solrQuery.q = `${field}:${value}`; + } else if (queryPart.range) { + const field = Object.keys(queryPart.range)[0]; + const range = queryPart.range[field]; + const rangeQuery = `${field}:[${range.gte || '*'} TO ${range.lte || '*'}]`; + solrQuery.fq = rangeQuery; + } else if (queryPart.match_all) { + solrQuery.q = `*:*`; + } + } else { + solrQuery.q = `*:*`; + } + + // Handle pagination + if (query.from !== undefined) { + if (typeof query.from == "boolean" && !query.from) { + solrQuery.start = 0 + } else { + solrQuery.start = query.from; + } + } + if (query.size !== undefined) { + if (typeof query.size == "boolean" && !query.size) { + solrQuery.rows = 10 + } else { + solrQuery.rows = query.size; + } + + } + + // Handle sorting + if (query && query.sort && query.sort.length > 0) { + solrQuery.sort = query.sort.map(sortOption => { + const sortField = sortOption.field; + const sortOrder = sortOption.order === "desc" ? "desc" : "asc"; + return `${sortField} ${sortOrder}`; + }).join(', '); + } + + if (query && query.aggs && query.aggs.length > 0) { + let facets = query.aggs.map(agg => this._convertAggToFacet(agg)); + solrQuery.factes = facets.join(','); + } + + solrQuery.wt = "json" + + return solrQuery; +} + +function _args2URL({ baseUrl, args }) { + const qParts = Object.keys(args).flatMap(k => { + const v = args[k]; + if (Array.isArray(v)) { + return v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`); + } + return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; + }); + + const qs = qParts.join("&"); + return `${baseUrl}?${qs}`; +} + +function _convertAggToFacet(agg) { + const field = agg.field; + const name = agg.name; + const size = agg.size || 10; // default size if not specified + const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr + const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified + + return `facet.field={!key=${name}}${field}&f.${field}.facet.limit=${size}&f.${field}.facet.sort=${order} ${direction}`; +} \ No newline at end of file diff --git a/src/edges.js b/src/edges.js index 376920b..b5f6f85 100644 --- a/src/edges.js +++ b/src/edges.js @@ -829,10 +829,18 @@ edges.es.SolrQueryAdapter = class extends edges.QueryAdapter { query = edge.currentQuery; } - const args = this._es2solr({ query : query }); + es.doQuery({ + search_url: edge.searchUrl, + query: query, + datatype: edge.datatype, + success: success, + error: error + }) + + // const args = this._es2solr({ query : query }); - // Execute the Solr query - this._solrQuery({ edge, success, error, solrArgs: args }); + // // Execute the Solr query + // this._solrQuery({ edge, success, error, solrArgs: args }); }; // Method to execute the Solr query @@ -897,7 +905,7 @@ edges.es.SolrQueryAdapter = class extends edges.QueryAdapter { } // Handle sorting - if (query && query.sort.length > 0) { + if (query && query.sort && query.sort.length > 0) { solrQuery.sort = query.sort.map(sortOption => { const sortField = sortOption.field; const sortOrder = sortOption.order === "desc" ? "desc" : "asc"; @@ -905,7 +913,7 @@ edges.es.SolrQueryAdapter = class extends edges.QueryAdapter { }).join(', '); } - if (query && query.aggs.length > 0) { + if (query && query.aggs && query.aggs.length > 0) { let facets = query.aggs.map(agg => this._convertAggToFacet(agg)); solrQuery.factes = facets.join(','); } @@ -945,7 +953,7 @@ edges.es.SolrQueryAdapter = class extends edges.QueryAdapter { return; } - var result = new SolrResult({raw: data}); + var result = new es.Result({raw: data}); callback(result); } } From c82aeffba7b15378416c3c43164a46197b299c9f Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Wed, 26 Jun 2024 13:38:33 +0530 Subject: [PATCH 02/21] Bug fixed for facets --- src/datasources/solr9x.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index e9545b7..1ab1bd3 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -1196,8 +1196,13 @@ function _es2solr({ query }) { } if (query && query.aggs && query.aggs.length > 0) { - let facets = query.aggs.map(agg => this._convertAggToFacet(agg)); - solrQuery.factes = facets.join(','); + let facetsFields = query.aggs.map(agg => this._convertAggFieldToFacetField(agg)); + query.aggs.forEach(agg => { + _convertAggLimitToFacetLimit(agg, solrQuery); + _convertAggSortToFacetSort(agg, solrQuery); + }); + solrQuery.facet = true + solrQuery["facet.field"] = facetsFields.join(",") } solrQuery.wt = "json" @@ -1218,12 +1223,24 @@ function _args2URL({ baseUrl, args }) { return `${baseUrl}?${qs}`; } -function _convertAggToFacet(agg) { +function _convertAggFieldToFacetField(agg) { const field = agg.field; const name = agg.name; - const size = agg.size || 10; // default size if not specified - const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr + + return `{!key=${name}}${field}`; +} + +function _convertAggLimitToFacetLimit(agg , solrQuery) { + const size = agg.size || 10; // default size if not specified + const field = agg.field; + + solrQuery[`f.${field}.facet.limit`] = size +} + +function _convertAggSortToFacetSort(agg , solrQuery) { + const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified + const field = agg.field; - return `facet.field={!key=${name}}${field}&f.${field}.facet.limit=${size}&f.${field}.facet.sort=${order} ${direction}`; + solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}` } \ No newline at end of file From 5cc003638ef511f3d1daebd21a565278d416a753 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Thu, 27 Jun 2024 13:58:50 +0530 Subject: [PATCH 03/21] Solr query and querystring is updated --- src/datasources/solr9x.js | 706 +++++++++++++++----------------------- 1 file changed, 272 insertions(+), 434 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index 1ab1bd3..755d4c8 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -97,41 +97,39 @@ es.Filter = class { parse() {} }; + +// Define the Query class es.Query = class { constructor(params) { - if (!params) { params = {}} - // properties that can be set directly - this.filtered = false; // this is no longer present in es5.x+ - this.trackTotalHits = true; // FIXME: hard code this for the moment, we can introduce the ability to vary it later - - this.size = this.getParam(params.size, false); - this.from = this.getParam(params.from, false); - this.fields = this.getParam(params.fields, []); - this.aggs = this.getParam(params.aggs, []); - this.must = this.getParam(params.must, []); - this.mustNot = this.getParam(params.mustNot, []); - this.should = this.getParam(params.should, []); - this.minimumShouldMatch = this.getParam(params.minimumShouldMatch, false); - - // defaults from properties that will be set through their setters + if (!params) { params = {}; } + + // Properties initialization + this.filtered = false; // no longer present in ES 5.x+ + this.trackTotalHits = true; // FIXME: hard code this for now + + // Initialize with default values or from params + this.size = es.getParam(params.size, false); + this.from = es.getParam(params.from, false); + this.fields = es.getParam(params.fields, []); + this.aggs = es.getParam(params.aggs, []); + this.must = es.getParam(params.must, []); + this.mustNot = es.getParam(params.mustNot, []); + this.should = es.getParam(params.should, []); + this.minimumShouldMatch = es.getParam(params.minimumShouldMatch, false); + + // Defaults from properties set through their setters this.queryString = false; this.sort = []; - // ones that we haven't used yet, so are awaiting implementation - // NOTE: once we implement these, they also need to be considered in merge() - this.source = this.getParam(params.source, false); - this.partialFields = this.getParam(params.partialField, false); - this.scriptFields = this.getParam(params.scriptFields, false); - this.partialFields = this.getParam(params.partialFields, false); - this.scriptFields = this.getParam(params.scriptFields, false); - - // for old versions of ES, so are not necessarily going to be implemented - this.facets = this.getParam(params.facets, []); + // Properties awaiting implementation + this.source = es.getParam(params.source, false); + this.partialFields = es.getParam(params.partialFields, false); // using partialFields instead of partialField + this.scriptFields = es.getParam(params.scriptFields, false); - /////////////////////////////////////////////////////////// - // final part of construction - set the dynamic properties - // via their setters + // For older ES versions, may not be implemented + this.facets = es.getParam(params.facets, []); + // Final part of construction - set dynamic properties via their setters if (params.queryString) { this.setQueryString(params.queryString); } @@ -140,43 +138,34 @@ es.Query = class { this.setSortBy(params.sort); } - // finally, if we're given a raw query, parse it + // Parse raw query if provided if (params.raw) { - this.parse(params.raw) + this.parse(params.raw); } } - getParam(param, defaultValue) { - return param !== undefined ? param : defaultValue; - } - + // Getters and Setters getSize() { - if (this.size !== undefined && this.size !== false) { - return this.size; - } - return 10; + return this.size !== undefined && this.size !== false ? this.size : 10; } getFrom() { - if (this.from) { - return this.from - } - return 0; + return this.from || 0; } addField(field) { - if (this.fields.indexOf(field) === -1) { + if (!this.fields.includes(field)) { this.fields.push(field); } } setQueryString(params) { - var qs = params; + let qs = params; if (!(params instanceof es.QueryString)) { - if (typeof params === 'object') { + if ($.isPlainObject(params)) { qs = new es.QueryString(params); } else { - qs = new es.QueryString({queryString: params}); + qs = new es.QueryString({ queryString: params }); } } this.queryString = qs; @@ -191,63 +180,27 @@ es.Query = class { } setSortBy(params) { - // overwrite anything that was there before this.sort = []; - // ensure we have a list of sort options - var sorts = params; - if (!Array.isArray(params)) { - sorts = [params] - } - // add each one - for (var i = 0; i < sorts.length; i++) { - this.addSortBy(sorts[i]); - } + let sorts = Array.isArray(params) ? params : [params]; + sorts.forEach(sort => this.addSortBy(sort)); } addSortBy(params) { - // ensure we have an instance of es.Sort - var sort = params; - if (!(params instanceof es.Sort)) { - sort = new es.Sort(params); - } - // prevent repeated sort options being added - for (var i = 0; i < this.sort.length; i++) { - var so = this.sort[i]; - if (so.field === sort.field) { - return; - } + let sort = params instanceof es.Sort ? params : new es.Sort(params); + if (!this.sort.some(existingSort => existingSort.field === sort.field)) { + this.sort.push(sort); } - // add the sort option - this.sort.push(sort); } prependSortBy(params) { - // ensure we have an instance of es.Sort - var sort = params; - if (!(params instanceof es.Sort)) { - sort = new es.Sort(params); - } + let sort = params instanceof es.Sort ? params : new es.Sort(params); this.removeSortBy(sort); this.sort.unshift(sort); } removeSortBy(params) { - // ensure we have an instance of es.Sort - var sort = params; - if (!(params instanceof es.Sort)) { - sort = new es.Sort(params); - } - var removes = []; - for (var i = 0; i < this.sort.length; i++) { - var so = this.sort[i]; - if (so.field === sort.field) { - removes.push(i); - } - } - removes = removes.sort().reverse(); - for (var i = 0; i < removes.length; i++) { - this.sort.splice(removes[i], 1); - } + let sort = params instanceof es.Sort ? params : new es.Sort(params); + this.sort = this.sort.filter(existingSort => existingSort.field !== sort.field); } getSortBy() { @@ -255,9 +208,7 @@ es.Query = class { } setSourceFilters(params) { - if (!this.source) { - this.source = {include: [], exclude: []}; - } + this.source = this.source || { include: [], exclude: [] }; if (params.include) { this.source.include = params.include; } @@ -267,73 +218,37 @@ es.Query = class { } addSourceFilters(params) { - if (!this.source) { - this.source = {include: [], exclude: []}; - } + this.source = this.source || { include: [], exclude: [] }; if (params.include) { - if (this.source.include) { - Array.prototype.push.apply(this.source.include, params.include); - } else { - this.source.include = params.include; - } + this.source.include.push(...params.include); } if (params.exclude) { - if (this.source.include) { - Array.prototype.push.apply(this.source.include, params.include); - } else { - this.source.include = params.include; - } + this.source.exclude.push(...params.exclude); } } getSourceIncludes() { - if (!this.source) { - return []; - } - return this.source.include; + return this.source && this.source.include ? this.source.include : []; } getSourceExcludes() { - if (!this.source) { - return []; - } - return this.source.exclude; - }; + return this.source && this.source.exclude ? this.source.exclude : []; + } + // Aggregation Methods getAggregation(params) { - var name = params.name; - for (var i = 0; i < this.aggs.length; i++) { - var a = this.aggs[i]; - if (a.name === name) { - return a; - } - } + return this.aggs.find(agg => agg.name === params.name); } addAggregation(agg, overwrite) { if (overwrite) { this.removeAggregation(agg.name); - } else { - for (var i = 0; i < this.aggs.length; i++) { - if (this.aggs[i].name === agg.name) { - return; - } - } } this.aggs.push(agg); } removeAggregation(name) { - var removes = []; - for (var i = 0; i < this.aggs.length; i++) { - if (this.aggs[i].name === name) { - removes.push(i); - } - } - removes = removes.sort().reverse(); - for (var i = 0; i < removes.length; i++) { - this.aggs.splice(removes[i], 1); - } + this.aggs = this.aggs.filter(agg => agg.name !== name); } clearAggregations() { @@ -344,31 +259,27 @@ es.Query = class { return this.aggs; } + // Filter Methods addMust(filter) { - var existing = this.listMust(filter); - if (existing.length === 0) { + if (!this.listMust().some(existingFilter => existingFilter.matches(filter))) { this.must.push(filter); } } - listMust(template) { - return this.listFilters({boolType: "must", template: template}); + listMust() { + return this.must; } removeMust(template) { - var removes = []; - for (var i = 0; i < this.must.length; i++) { - var m = this.must[i]; - if (m.matches(template)) { - removes.push(i); + let removedCount = 0; + this.must = this.must.filter(filter => { + const matches = filter.matches(template); + if (matches) { + removedCount++; } - } - removes = removes.sort().reverse(); - for (var i = 0; i < removes.length; i++) { - this.must.splice(removes[i], 1); - } - // return the count of filters that were removed - return removes.length; + return !matches; + }); + return removedCount; } clearMust() { @@ -376,30 +287,25 @@ es.Query = class { } addMustNot(filter) { - var existing = this.listMustNot(filter); - if (existing.length === 0) { + if (!this.listMustNot().some(existingFilter => existingFilter.matches(filter))) { this.mustNot.push(filter); } } - listMustNot(template) { - return this.listFilters({boolType: "must_not", template: template}); + listMustNot() { + return this.mustNot; } removeMustNot(template) { - var removes = []; - for (var i = 0; i < this.mustNot.length; i++) { - var m = this.mustNot[i]; - if (m.matches(template)) { - removes.push(i); + let removedCount = 0; + this.mustNot = this.mustNot.filter(filter => { + const matches = filter.matches(template); + if (matches) { + removedCount++; } - } - removes = removes.sort().reverse(); - for (var i = 0; i < removes.length; i++) { - this.mustNot.splice(removes[i], 1); - } - // return the count of filters that were removed - return removes.length; + return !matches; + }); + return removedCount; } clearMustNot() { @@ -407,279 +313,178 @@ es.Query = class { } addShould(filter) { - var existing = this.listShould(filter); - if (existing.length === 0) { + if (!this.listShould().some(existingFilter => existingFilter.matches(filter))) { this.should.push(filter); } } - listShould(template) { - return this.listFilters({boolType: "should", template: template}); + listShould() { + return this.should; } removeShould(template) { - var removes = []; - for (var i = 0; i < this.should.length; i++) { - var m = this.should[i]; - if (m.matches(template)) { - removes.push(i); + let removedCount = 0; + this.should = this.should.filter(filter => { + const matches = filter.matches(template); + if (matches) { + removedCount++; } - } - removes = removes.sort().reverse(); - for (var i = 0; i < removes.length; i++) { - this.should.splice(removes[i], 1); - } - // return the count of filters that were removed - return removes.length; + return !matches; + }); + return removedCount; } clearShould() { this.should = []; } - setMinimumShouldMatch(val) { - this.minimumShouldMatch = val; - } - - getMinimumShouldMatch() { - return this.minimumShouldMatch; + // Interrogative Methods + hasFilters() { + return this.must.length > 0 || this.should.length > 0 || this.mustNot.length > 0; } listFilters(params) { - var boolType = params.boolType; - var template = params.template; - var filters = []; - var source; - if (boolType === "must") { - source = this.must; - } else if (boolType === "must_not") { - source = this.mustNot; - } else if (boolType === "should") { - source = this.should; - } - for (var i = 0; i < source.length; i++) { - var f = source[i]; - if (f.matches(template)) { - filters.push(f); - } - } - return filters; - } - - getFilters() { - var filters = []; - for (var i = 0; i < this.must.length; i++) { - filters.push(this.must[i]); - } - for (var i = 0; i < this.mustNot.length; i++) { - filters.push(this.mustNot[i]); - } - for (var i = 0; i < this.should.length; i++) { - filters.push(this.should[i]); - } - return filters; - } - - hasFilters() { - if (this.must.length > 0 || this.mustNot.length > 0 || this.should.length > 0) { - return true; + const { boolType, template } = params; + switch (boolType) { + case 'must': + return this.listMust().filter(filter => filter.matches(template)); + case 'should': + return this.listShould().filter(filter => filter.matches(template)); + case 'must_not': + return this.listMustNot().filter(filter => filter.matches(template)); + default: + return []; } - return false; } - parse(raw) { - var parsed = JSON.parse(raw); - // re-initialise - this.must = []; - this.mustNot = []; - this.should = []; - this.minimumShouldMatch = false; - // and then apply - this.apply(parsed); - } - - apply(parsed) { - if (parsed.size !== undefined) { - this.size = parsed.size; - } - if (parsed.from !== undefined) { - this.from = parsed.from; + // Parsing and Serialization + merge(source) { + this.filtered = source.filtered; + if (source.size) { + this.size = source.size; } - if (parsed.track_total_hits !== undefined) { - this.trackTotalHits = parsed.track_total_hits; + if (source.from) { + this.from = source.from; } - if (parsed._source) { - this.setSourceFilters(parsed._source); + if (source.fields && source.fields.length > 0) { + source.fields.forEach(field => this.addField(field)); } - if (parsed.fields) { - this.fields = parsed.fields; + source.aggs.forEach(agg => this.addAggregation(agg, true)); + source.must.forEach(filter => this.addMust(filter)); + source.mustNot.forEach(filter => this.addMustNot(filter)); + source.should.forEach(filter => this.addShould(filter)); + if (source.minimumShouldMatch !== false) { + this.minimumShouldMatch = source.minimumShouldMatch; } - if (parsed.sort) { - this.setSortBy(parsed.sort); + if (source.getQueryString()) { + this.setQueryString(source.getQueryString()); } - if (parsed.aggs) { - for (var a in parsed.aggs) { - this.addAggregation(new es.Aggregation({name: a, raw: parsed.aggs[a]})); - } + if (source.sort && source.sort.length > 0) { + source.sort.reverse().forEach(sort => this.prependSortBy(sort)); } - if (parsed.query) { - if (parsed.query.query_string) { - this.setQueryString(parsed.query.query_string); - } - if (parsed.query.bool) { - if (parsed.query.bool.must) { - for (var i = 0; i < parsed.query.bool.must.length; i++) { - this.addMust(new es.Filter({raw: parsed.query.bool.must[i]})); - } - } - if (parsed.query.bool.must_not) { - for (var i = 0; i < parsed.query.bool.must_not.length; i++) { - this.addMustNot(new es.Filter({raw: parsed.query.bool.must_not[i]})); - } - } - if (parsed.query.bool.should) { - for (var i = 0; i < parsed.query.bool.should.length; i++) { - this.addShould(new es.Filter({raw: parsed.query.bool.should[i]})); - } - } - if (parsed.query.bool.minimum_should_match) { - this.setMinimumShouldMatch(parsed.query.bool.minimum_should_match); - } - } + if (source.source) { + this.addSourceFilters({ include: source.getSourceIncludes(), exclude: source.getSourceExcludes() }); } } - export() { - var exported = { - // see https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html - size: this.size || 10, - from: this.from || 0, - track_total_hits: this.trackTotalHits, - query: {}, - aggs: {}, - _source: {}, - sort: [] - }; - if (this.getSourceIncludes().length > 0 || this.getSourceExcludes().length > 0) { - exported._source = { - includes: this.getSourceIncludes(), - excludes: this.getSourceExcludes() - }; - } - if (this.fields.length > 0) { - exported.fields = this.fields; - } - if (this.sort.length > 0) { - for (var i = 0; i < this.sort.length; i++) { - exported.sort.push(this.sort[i].export()); - } - } else { - delete exported.sort; + objectify(params) { + params = params || {}; + const { + include_query_string = true, + include_filters = true, + include_paging = true, + include_sort = true, + include_fields = true, + include_aggregations = true, + include_source_filters = true + } = params; + + const query_part = {}; + const bool = {}; + + if (this.queryString && include_query_string) { + Object.assign(query_part, this.queryString.objectify()); } - var filters = this.getFilters(); - if (filters.length > 0) { - exported.query.bool = {}; + + if (include_filters) { if (this.must.length > 0) { - exported.query.bool.must = []; - for (var i = 0; i < this.must.length; i++) { - exported.query.bool.must.push(this.must[i].export()); - } + bool.must = this.must.map(filter => filter.objectify()); } if (this.mustNot.length > 0) { - exported.query.bool.must_not = []; - for (var i = 0; i < this.mustNot.length; i++) { - exported.query.bool.must_not.push(this.mustNot[i].export()); - } + bool.must_not = this.mustNot.map(filter => filter.objectify()); } if (this.should.length > 0) { - exported.query.bool.should = []; - for (var i = 0; i < this.should.length; i++) { - exported.query.bool.should.push(this.should[i].export()); - } + bool.should = this.should.map(filter => filter.objectify()); } - if (this.minimumShouldMatch) { - exported.query.bool.minimum_should_match = this.minimumShouldMatch; + if (this.minimumShouldMatch !== false) { + bool.minimum_should_match = this.minimumShouldMatch; } - } else if (this.queryString) { - exported.query = { - query_string: this.queryString.export() - }; - } else { - exported.query = { - match_all: {} - }; } - for (var i = 0; i < this.aggs.length; i++) { - var agg = this.aggs[i]; - var inner = {}; - inner[agg.type] = {}; - if (agg.field) { - inner[agg.type].field = agg.field; - } - if (agg.script) { - inner[agg.type].script = agg.script; - } - if (agg.size) { - inner[agg.type].size = agg.size; - } - if (agg.interval) { - inner[agg.type].interval = agg.interval; - } - exported.aggs[agg.name] = inner; - } - // if we haven't added any aggs, remove the aggs key - if (Object.keys(exported.aggs).length === 0) { - delete exported.aggs; - } - // remove the _source key if it wasn't populated - if (Object.keys(exported._source).length === 0) { - delete exported._source; - } - return JSON.stringify(exported, null, 4); - } - merge(q) { - // size / limit - if (q.size) { - this.size = q.size; + if (Object.keys(query_part).length === 0 && Object.keys(bool).length === 0) { + query_part.match_all = {}; + } else if (Object.keys(query_part).length === 0 && Object.keys(bool).length > 0) { + query_part.bool = bool; } - // from / offset - if (q.from) { - this.from = q.from; - } - // fields - for (var i = 0; i < q.fields.length; i++) { - this.addField(q.fields[i]); - } - // aggregations - for (var i = 0; i < q.aggs.length; i++) { - this.addAggregation(q.aggs[i]); - } - // musts - for (var i = 0; i < q.must.length; i++) { - this.addMust(q.must[i]); - } - // must nots - for (var i = 0; i < q.mustNot.length; i++) { - this.addMustNot(q.mustNot[i]); + + const obj = { + query: query_part + }; + + if (include_paging) { + obj.from = this.getFrom(); + obj.size = this.getSize(); } - // shoulds - for (var i = 0; i < q.should.length; i++) { - this.addShould(q.should[i]); + + if (include_sort && this.sort.length > 0) { + obj.sort = this.sort.map(sort => sort.objectify()); } - if (q.minimumShouldMatch) { - this.setMinimumShouldMatch(q.minimumShouldMatch); + + if (include_fields && this.fields.length > 0) { + obj.fields = this.fields.slice(); // Shallow copy of fields array } - // sort - for (var i = 0; i < q.sort.length; i++) { - this.addSortBy(q.sort[i]); + + if (include_aggregations && this.aggs.length > 0) { + obj.aggs = this.aggs.map(agg => agg.objectify()); } - // query string - if (q.queryString) { - this.setQueryString(q.queryString); + + if (include_source_filters && this.source) { + obj._source = {}; + if (this.source.include && this.source.include.length > 0) { + obj._source.includes = this.source.include.slice(); // Shallow copy of include array + } + if (this.source.exclude && this.source.exclude.length > 0) { + obj._source.excludes = this.source.exclude.slice(); // Shallow copy of exclude array + } } + + return obj; } -}; + + clone() { + const cloneParams = { + size: this.size, + from: this.from, + fields: [...this.fields], // Shallow copy of fields array + aggs: this.aggs.map(agg => ({ ...agg })), // Shallow copy of aggs array + must: this.must.map(filter => ({ ...filter })), // Shallow copy of must array + mustNot: this.mustNot.map(filter => ({ ...filter })), // Shallow copy of mustNot array + should: this.should.map(filter => ({ ...filter })), // Shallow copy of should array + minimumShouldMatch: this.minimumShouldMatch, + queryString: this.queryString ? { ...this.queryString } : null, // Shallow copy of queryString if present + sort: this.sort.map(sort => ({ ...sort })), // Shallow copy of sort array + source: this.source ? { + include: [...this.source.include], // Shallow copy of include array + exclude: [...this.source.exclude] // Shallow copy of exclude array + } : null, + partialFields: this.partialFields, + scriptFields: this.scriptFields + // Add any other properties that need to be cloned + }; + + return new es.Query(cloneParams); + } +} es.QueryString = class { constructor(params) { @@ -698,8 +503,8 @@ es.QueryString = class { } objectify() { - var qs = this._escape(this._fuzzify(this.queryString)); - var obj = {q: qs}; + const qs = this._escape(this._fuzzify(this.queryString)); + const obj = { q: qs }; if (this.defaultOperator) { obj["q.op"] = this.defaultOperator; } @@ -709,6 +514,18 @@ es.QueryString = class { return obj; } + clone() { + return new es.QueryString({ + queryString: this.queryString, + defaultField: this.defaultField, + defaultOperator: this.defaultOperator, + fuzzify: this.fuzzify, + escapeSet: this.escapeSet.slice(), // Shallow copy of escapeSet array + pairs: this.pairs.slice(), // Shallow copy of pairs array + unEscapeSet: this.unEscapeSet.slice() // Shallow copy of unEscapeSet array + }); + } + parse(obj) { if (obj.q) { this.queryString = this._unescape(obj.q); @@ -726,71 +543,65 @@ es.QueryString = class { return str; } - if (!(str.indexOf('*') === -1 && str.indexOf('~') === -1 && str.indexOf(':') === -1)) { + if (!(str.includes('*') || str.includes('~') || str.includes(':'))) { return str; } - var pq = ""; - var optparts = str.split(' '); - for (var i = 0; i < optparts.length; i++) { - var oip = optparts[i]; + let pq = ""; + const optparts = str.split(' '); + for (let i = 0; i < optparts.length; i++) { + let oip = optparts[i]; if (oip.length > 0) { - oip = oip + this.fuzzify; - this.fuzzify === "*" ? oip = "*" + oip : false; + oip += this.fuzzify; + if (this.fuzzify === "*") { + oip = "*" + oip; + } pq += oip + " "; } } - return pq; - }; + return pq.trim(); + } _escapeRegExp(string) { return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); - }; + } _replaceAll(string, find, replace) { return string.replace(new RegExp(this._escapeRegExp(find), 'g'), replace); - }; + } _unReplaceAll(string, find) { - return string.replace(new RegExp("\\\\(" + this._escapeRegExp(find) + ")", 'g'), "$1"); - }; + return string.replace(new RegExp("\\\\" + this._escapeRegExp(find), 'g'), find); + } _paired(string, pair) { - var matches = (string.match(new RegExp(this._escapeRegExp(pair), "g"))) || []; + const matches = (string.match(new RegExp(this._escapeRegExp(pair), "g"))) || []; return matches.length % 2 === 0; - }; + } _escape(str) { - // make a copy of the special characters (we may modify it in a moment) - var scs = this.escapeSet.slice(0); - - // first check for pairs, and push any extra characters to be escaped - for (var i = 0; i < this.pairs.length; i++) { - var char = this.pairs[i]; + let scs = this.escapeSet.slice(); // Make a copy of escapeSet + for (let i = 0; i < this.pairs.length; i++) { + const char = this.pairs[i]; if (!this._paired(str, char)) { scs.push(char); } } - - // now do the escape - for (var i = 0; i < scs.length; i++) { - var char = scs[i]; + for (let i = 0; i < scs.length; i++) { + const char = scs[i]; str = this._replaceAll(str, char, "\\" + char); } - return str; - }; + } _unescape(str) { - for (var i = 0; i < this.unEscapeSet.length; i++) { - var char = this.unEscapeSet[i]; - str = this._unReplaceAll(str, char) + for (let i = 0; i < this.unEscapeSet.length; i++) { + const char = this.unEscapeSet[i]; + str = this._unReplaceAll(str, char); } return str; - }; -} - - + } +}; // Factories es.aggregationFactory = function(type, params) { @@ -1051,7 +862,6 @@ es.doQuery = (params) => { const { success, error, complete, search_url, query, datatype } = params; const solrArgs = this._es2solr({ query : query }); - const searchUrl = search_url; // Generate the Solr query URL const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); @@ -1195,6 +1005,29 @@ function _es2solr({ query }) { }).join(', '); } + if (query.queryString && query.queryString.queryString) { + const esQueryString = query.queryString.queryString; + const searchField = query.queryString.defaultField; + let operator = query.queryString.defaultOperator; + + if(typeof esQueryString == 'boolean') { + throw new Error('Search string needs to be string got boolean'); + } + + if (operator == '') { + operator = "OR" + } + + if (esQueryString != "") { + if (typeof searchField == 'boolean') { + solrQuery.q = `${solrQuery.q} ${operator} ${esQueryString}`; + } else { + solrQuery.q = `${solrQuery.q} ${operator} ${searchField}:${esQueryString}`; + } + } + } + + if (query && query.aggs && query.aggs.length > 0) { let facetsFields = query.aggs.map(agg => this._convertAggFieldToFacetField(agg)); query.aggs.forEach(agg => { @@ -1243,4 +1076,9 @@ function _convertAggSortToFacetSort(agg , solrQuery) { const field = agg.field; solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}` +} + + +es.getParam = function(value, def) { + return value !== undefined ? value : def; } \ No newline at end of file From e3ff815a120cef0e23e7f8545e9933fb2ab5f46e Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Thu, 27 Jun 2024 15:06:10 +0530 Subject: [PATCH 04/21] Must, MustNot and should functions are corrected --- src/datasources/solr9x.js | 54 +++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index 755d4c8..dc0cde3 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -261,7 +261,9 @@ es.Query = class { // Filter Methods addMust(filter) { - if (!this.listMust().some(existingFilter => existingFilter.matches(filter))) { + if (!this.listMust().some(existingFilter => { + return Object.keys(filter).every(key => existingFilter[key] === filter[key]); + })) { this.must.push(filter); } } @@ -273,7 +275,9 @@ es.Query = class { removeMust(template) { let removedCount = 0; this.must = this.must.filter(filter => { - const matches = filter.matches(template); + + // Check if filter values match the template values + const matches = Object.keys(template).every(key => filter[key] === template[key]); if (matches) { removedCount++; } @@ -281,13 +285,14 @@ es.Query = class { }); return removedCount; } - clearMust() { this.must = []; } addMustNot(filter) { - if (!this.listMustNot().some(existingFilter => existingFilter.matches(filter))) { + if (!this.listMustNot().some(existingFilter => { + return Object.keys(filter).every(key => existingFilter[key] === filter[key]); + })) { this.mustNot.push(filter); } } @@ -299,7 +304,7 @@ es.Query = class { removeMustNot(template) { let removedCount = 0; this.mustNot = this.mustNot.filter(filter => { - const matches = filter.matches(template); + const matches = Object.keys(template).every(key => filter[key] === template[key]); if (matches) { removedCount++; } @@ -313,7 +318,9 @@ es.Query = class { } addShould(filter) { - if (!this.listShould().some(existingFilter => existingFilter.matches(filter))) { + if (!this.listShould().some(existingFilter => { + return Object.keys(filter).every(key => existingFilter[key] === filter[key]); + })) { this.should.push(filter); } } @@ -325,7 +332,7 @@ es.Query = class { removeShould(template) { let removedCount = 0; this.should = this.should.filter(filter => { - const matches = filter.matches(template); + const matches = Object.keys(template).every(key => filter[key] === template[key]); if (matches) { removedCount++; } @@ -345,17 +352,21 @@ es.Query = class { listFilters(params) { const { boolType, template } = params; + const matchesTemplate = filter => { + return Object.keys(template).every(key => filter[key] === template[key]); + }; + switch (boolType) { case 'must': - return this.listMust().filter(filter => filter.matches(template)); + return this.listMust().filter(matchesTemplate); case 'should': - return this.listShould().filter(filter => filter.matches(template)); + return this.listShould().filter(matchesTemplate); case 'must_not': - return this.listMustNot().filter(filter => filter.matches(template)); + return this.listMustNot().filter(matchesTemplate); default: return []; } - } + } // Parsing and Serialization merge(source) { @@ -865,7 +876,7 @@ es.doQuery = (params) => { const searchUrl = search_url; // Generate the Solr query URL const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); - + var error_callback = es.queryError(error); var success_callback = es.querySuccess(success, error_callback); @@ -1027,7 +1038,6 @@ function _es2solr({ query }) { } } - if (query && query.aggs && query.aggs.length > 0) { let facetsFields = query.aggs.map(agg => this._convertAggFieldToFacetField(agg)); query.aggs.forEach(agg => { @@ -1038,6 +1048,24 @@ function _es2solr({ query }) { solrQuery["facet.field"] = facetsFields.join(",") } + if(query && query.must && query.must.length > 0) { + query.must.forEach(mustQuery => { + solrQuery.q = `${mustQuery.field}:${mustQuery.value}` + }); + } + + if(query && query.mustNot && query.mustNot.length > 0) { + query.mustNot.forEach(mustNotq => { + solrQuery.q = `-${mustNotq.field}:${mustNotq.value}` + }); + } + + if(query && query.should && query.should.length > 0) { + query.should.forEach(shouldQ => { + solrQuery.q = `(${shouldQ.field}:${shouldQ.value})^1.0` + }); + } + solrQuery.wt = "json" return solrQuery; From cb7d4281e652c66799595fed104b2535313c2067 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Fri, 28 Jun 2024 10:07:12 +0530 Subject: [PATCH 05/21] Geo agg and filter classes and other missing functionality is been added --- src/datasources/solr9x.js | 567 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 567 insertions(+) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index dc0cde3..e6dbf8c 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -2,6 +2,14 @@ if (!window.hasOwnProperty("es")) { es = {}} // request method to be used throughout. Set this before using the module if you want it different es.requestMethod = "get"; +es.distanceUnits = ["km", "mi", "miles", "in", "inch", "yd", "yards", "kilometers", "mm", "millimeters", "cm", "centimeters", "m", "meters"]; + +es.specialChars = ["\\", "+", "-", "=", "&&", "||", ">", "<", "!", "(", ")", "{", "}", "[", "]", "^", '"', "~", "*", "?", ":", "/"]; +es.characterPairs = ['"']; + +// the reserved special character set with * and " removed, so that users can do quote searches and wildcards +// if they want +es.specialCharsSubSet = ["\\", "+", "-", "=", "&&", "||", ">", "<", "!", "(", ")", "{", "}", "[", "]", "^", "~", "?", ":", "/"]; // add request headers (such as Auth) if you need to es.requestHeaders = false; @@ -97,6 +105,18 @@ es.Filter = class { parse() {} }; +es._classExtends = function(clazz, ref) { + if (clazz.__proto__ === null) { + return false; + } + if (clazz.__proto__ === ref) { + return true; + } + else { + return es._classExtends(clazz.__proto__, ref); + } +} + // Define the Query class es.Query = class { @@ -701,6 +721,29 @@ es.TermFilter = class extends es.Filter { } } +es.ExistsFilter = class extends es.Filter { + static type = "exists"; + + constructor(params) { + super(params); + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + return {exists: {field: this.field}}; + } + + parse(obj) { + if (obj.exists) { + obj = obj.exists; + } + this.field = obj.field; + } +} + es.TermsFilter = class extends es.Filter { static type = "terms"; @@ -793,6 +836,304 @@ es.TermsFilter = class extends es.Filter { } }; +es.BoolFilter = class extends es.Filter { + static type = "bool"; + + constructor(params) { + params["field"] = "_bool"; // Set a placeholder field for the filter + super(params); + + // For the moment, this implementation only supports must_not as it's all we need + this.mustNot = es.getParam(params.mustNot, []); + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + let obj = {bool: {}}; + if (this.mustNot.length > 0) { + let mustNots = []; + for (var i = 0; i < this.mustNot.length; i++) { + var m = this.mustNot[i]; + mustNots.push(m.objectify()); + } + obj.bool["must_not"] = mustNots; + } + return obj; + } + + parse(obj) { + if (obj.bool.must_not) { + for (var i = 0; i < obj.bool.must_not.length; i++) { + var type = Object.keys(obj.bool.must_not[i])[0]; + var fil = es.filterFactory(type, {raw: obj.bool.must_not[i]}); + if (fil) { + this.addMustNot(fil); + } + } + } + } + + addMustNot(filter) { + var existing = this.listMustNot(filter); + if (existing.length === 0) { + this.mustNot.push(filter); + } + } + + listMustNot(template) { + return this.listFilters({boolType: "must_not", template: template}); + } + + listFilters(params) { + var boolType = params.boolType || "must"; + var template = params.template || false; + + var bool = []; + if (boolType === "must") { + bool = this.aggs; // Using `this.aggs` assuming it holds the list of aggregations in Solr context + } else if (boolType === "should") { + bool = []; // Adjust as per your Solr implementation for 'should' clauses + } else if (boolType === "must_not") { + bool = this.mustNot; + } + + if (!template) { + return bool; + } + var l = []; + for (var i = 0; i < bool.length; i++) { + var m = bool[i]; + if (m.matches(template)) { + l.push(m); + } + } + return l; + } +} + +es.RangeFilter = class extends es.Filter { + static type = "range"; + + constructor(params) { + super(params); + + // Field handled by superclass + this.lt = es.getParam(params.lt, false); + this.lte = es.getParam(params.lte, false); + this.gte = es.getParam(params.gte, false); + this.format = es.getParam(params.format, false); + + // Normalize the values to strings + if (this.lt !== false) { + this.lt = this.lt.toString(); + } + if (this.lte !== false) { + this.lte = this.lte.toString(); + } + if (this.gte !== false) { + this.gte = this.gte.toString(); + } + + if (params.raw) { + this.parse(params.raw); + } + } + + matches(other) { + // Ask the parent object first + let pm = super.matches(other); + if (!pm) { + return false; + } + + // Ranges (if set) must match + if (other.lt !== undefined) { + if (other.lt.toString() !== this.lt) { + return false; + } + } + if (other.lte !== undefined) { + if (other.lte.toString() !== this.lte) { + return false; + } + } + if (other.gte !== undefined) { + if (other.gte.toString() !== this.gte) { + return false; + } + } + + if (other.format !== undefined) { + if (other.format !== this.format) { + return false; + } + } + + return true; + } + + objectify() { + var obj = {range: {}}; + obj.range[this.field] = {}; + if (this.lte !== false) { + obj.range[this.field]["lte"] = this.lte; + } + if (this.lt !== false && this.lte === false) { + obj.range[this.field]["lt"] = this.lt; + } + if (this.gte !== false) { + obj.range[this.field]["gte"] = this.gte; + } + if (this.format !== false) { + obj.range[this.field]["format"] = this.format; + } + return obj; + } + + parse(obj) { + if (obj.range) { + obj = obj.range; + } + this.field = Object.keys(obj)[0]; + if (obj[this.field].lte !== undefined) { + this.lte = obj[this.field].lte.toString(); + } + if (obj[this.field].lt !== undefined) { + this.lt = obj[this.field].lt.toString(); + } + if (obj[this.field].gte !== undefined) { + this.gte = obj[this.field].gte.toString(); + } + if (obj[this.field].format !== undefined) { + this.format = obj[this.field].format; + } + } +} + +es.GeoDistanceRangeFilter = class extends es.Filter { + static type = "geo_distance_range"; + + constructor(params) { + super(params); + + // Field is handled by superclass + this.lt = params.lt || false; + this.gte = params.gte || false; + this.lat = params.lat || false; + this.lon = params.lon || false; + this.unit = params.unit || "m"; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var obj = {geo_distance_range: {}}; + obj.geo_distance_range[this.field] = {lat: this.lat, lon: this.lon}; + if (this.lt) { + obj.geo_distance_range[this.field]["lt"] = this.lt + this.unit; + } + if (this.gte) { + obj.geo_distance_range[this.field]["gte"] = this.gte + this.unit; + } + return obj; + } + + parse(obj) { + function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } + + function splitUnits(str) { + var unit = false; + for (var i = 0; i < es.distanceUnits.length; i++) { + var cu = es.distanceUnits[i]; + if (endsWith(str, cu)) { + str = str.substring(0, str.length - cu.length); + unit = cu; // Solr uses full unit name directly + break; + } + } + return [str, unit]; + } + + if (obj.geo_distance_range) { + obj = obj.geo_distance_range; + } + this.field = Object.keys(obj)[0]; + this.lat = obj[this.field].lat; + this.lon = obj[this.field].lon; + + var lt = obj[this.field].lt; + var gte = obj[this.field].gte; + + if (lt) { + lt = lt.trim(); + var parts = splitUnits(lt); + this.lt = parts[0]; + this.unit = parts[1]; + } + + if (gte) { + gte = gte.trim(); + var parts = splitUnits(gte); + this.gte = parts[0]; + this.unit = parts[1]; + } + } +} + +es.GeoBoundingBoxFilter = class extends es.Filter { + static type = "geo_bounding_box"; + + constructor(params) { + super(params); + this.top_left = params.top_left || false; + this.bottom_right = params.bottom_right || false; + + if (params.raw) { + this.parse(params.raw); + } + } + + matches(other) { + // Ask the parent object first + let pm = super.matches(other); + if (!pm) { + return false; + } + if (other.top_left && other.top_left !== this.top_left) { + return false; + } + if (other.bottom_right && other.bottom_right !== this.bottom_right) { + return false; + } + return true; + } + + objectify() { + var obj = {geo_bounding_box: {}}; + obj.geo_bounding_box[this.field] = { + top_left: this.top_left, + bottom_right: this.bottom_right + }; + return obj; + } + + parse(obj) { + if (obj.geo_bounding_box) { + obj = obj.geo_bounding_box; + } + this.field = Object.keys(obj)[0]; + this.top_left = obj[this.field].top_left; + this.bottom_right = obj[this.field].bottom_right; + } +} + // Aggregation extended classes starts here es.TermsAggregation = class extends es.Aggregation { @@ -867,7 +1208,233 @@ es.TermsAggregation = class extends es.Aggregation { } }; +es.CardinalityAggregation = class extends es.Aggregation { + static type = "cardinality"; + + constructor(params) { + super(params); + this.field = es.getParam(params.field, false); + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = {field: this.field}; + return this._make_aggregation(es.CardinalityAggregation.type, body); + } + + parse(obj) { + var body = this._parse_wrapper(obj, es.CardinalityAggregation.type); + this.field = body.field; + } +} + +es.RangeAggregation = class extends es.Aggregation { + static type = "range"; + + constructor(params) { + super(params); + this.field = params.field || false; + this.ranges = params.ranges || []; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = {field: this.field, ranges: this.ranges}; + return this._make_aggregation(es.RangeAggregation.type, body); + } + parse(obj) { + var body = this._parse_wrapper(obj, es.RangeAggregation.type); + this.field = body.field; + this.ranges = body.ranges; + } +} + +es.GeoDistanceAggregation = class extends es.Aggregation { + static type = "geo_distance"; + + constructor(params) { + super(params); + this.field = params.field || false; + this.lat = params.lat || false; + this.lon = params.lon || false; + this.unit = params.unit || "m"; + this.distance_type = params.distance_type || "arc"; // Solr uses "arc" instead of "sloppy_arc" + this.ranges = params.ranges || []; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = { + field: this.field, + origin: `${this.lat},${this.lon}`, // Solr uses lat,lon as a string + unit: this.unit, + distance_type: this.distance_type, + ranges: this.ranges + }; + return this._make_aggregation(es.GeoDistanceAggregation.type, body); + } + + parse(obj) { + var body = this._parse_wrapper(obj, es.GeoDistanceAggregation.type); + this.field = body.field; + + var origin = body.origin.split(","); + if (origin.length === 2) { + this.lat = parseFloat(origin[0]); + this.lon = parseFloat(origin[1]); + } + + if (body.unit) { + this.unit = body.unit; + } + + if (body.distance_type) { + this.distance_type = body.distance_type; + } + + this.ranges = body.ranges; + } +} + +es.GeohashGridAggregation = class extends es.Aggregation { + static type = "geohash_grid"; + + constructor(params) { + super(params); + this.field = params.field || false; + this.precision = params.precision || 3; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = { + field: this.field, + precision: this.precision + }; + return this._make_aggregation(es.GeohashGridAggregation.type, body); + } + + parse(obj) { + var body = this._parse_wrapper(obj, es.GeohashGridAggregation.type); + this.field = body.field; + this.precision = body.precision; + } +} + +es.StatsAggregation = class extends es.Aggregation { + static type = "stats"; + + constructor(params) { + super(params); + this.field = params.field || false; + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = {field: this.field}; + return this._make_aggregation(es.StatsAggregation.type, body); + } + + parse(obj) { + var body = this._parse_wrapper(obj, es.StatsAggregation.type); + this.field = body.field; + } +} + +es.SumAggregation = class extends es.Aggregation { + static type = "sum"; + + constructor(params) { + super(params); + this.field = params.field || false; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = {field: this.field}; + return this._make_aggregation(es.SumAggregation.type, body); + } + + parse(obj) { + var body = this._parse_wrapper(obj, es.SumAggregation.type); + this.field = body.field; + } +} + +es.DateHistogramAggregation = class extends es.Aggregation { + static type = "date_histogram"; + + constructor(params) { + super(params); + this.field = params.field || false; + this.interval = params.interval || "month"; + this.format = params.format || false; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = {field: this.field, interval: this.interval}; + if (this.format) { + body["format"] = this.format; + } + return this._make_aggregation(es.DateHistogramAggregation.type, body); + } + + parse(obj) { + var body = this._parse_wrapper(obj, es.DateHistogramAggregation.type); + this.field = body.field; + if (body.interval) { + this.interval = body.interval; + } + if (body.format) { + this.format = body.format; + } + } +} + +es.FiltersAggregation = class extends es.Aggregation { + static type = "filters"; + + constructor(params) { + super(params); + this.filters = params.filters || {}; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + var body = {filters: this.filters}; + return this._make_aggregation(es.FiltersAggregation.type, body); + } + + parse(obj) { + var body = this._parse_wrapper(obj, es.FiltersAggregation.type); + this.filters = body.filters; + } +} es.doQuery = (params) => { const { success, error, complete, search_url, query, datatype } = params; From fc60cb5beb77fb3d4b6462673515a41f9366fff8 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Mon, 1 Jul 2024 21:31:49 +0530 Subject: [PATCH 06/21] Dead code removed --- src/edges.js | 130 --------------------------------------------------- 1 file changed, 130 deletions(-) diff --git a/src/edges.js b/src/edges.js index b5f6f85..cef3fec 100644 --- a/src/edges.js +++ b/src/edges.js @@ -836,137 +836,7 @@ edges.es.SolrQueryAdapter = class extends edges.QueryAdapter { success: success, error: error }) - - // const args = this._es2solr({ query : query }); - - // // Execute the Solr query - // this._solrQuery({ edge, success, error, solrArgs: args }); }; - - // Method to execute the Solr query - _solrQuery({ solrArgs, edge, success, error }) { - const searchUrl = edge.searchUrl; - - // Generate the Solr query URL - const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); - - var error_callback = this._queryError(error); - var success_callback = this._querySuccess(success, error_callback); - - // Perform the HTTP GET request to Solr - $.get({ - url: fullUrl, - datatype: edge ? edge.datatype : "jsonp", - success: success_callback, - error: error_callback, - jsonp: 'json.wrf' - }); - } - - // Method to convert es query to Solr query - _es2solr({ query }) { - const solrQuery = {}; - let solrFacets = [] - - // Handle the query part - if (query.query) { - const queryPart = query.query; - if (queryPart.match) { - const field = Object.keys(queryPart.match)[0]; - const value = queryPart.match[field]; - solrQuery.q = `${field}:${value}`; - } else if (queryPart.range) { - const field = Object.keys(queryPart.range)[0]; - const range = queryPart.range[field]; - const rangeQuery = `${field}:[${range.gte || '*'} TO ${range.lte || '*'}]`; - solrQuery.fq = rangeQuery; - } else if (queryPart.match_all) { - solrQuery.q = `*:*`; - } - } else { - solrQuery.q = `*:*`; - } - - // Handle pagination - if (query.from !== undefined) { - if (typeof query.from == "boolean" && !query.from) { - solrQuery.start = 0 - } else { - solrQuery.start = query.from; - } - } - if (query.size !== undefined) { - if (typeof query.size == "boolean" && !query.size) { - solrQuery.rows = 10 - } else { - solrQuery.rows = query.size; - } - - } - - // Handle sorting - if (query && query.sort && query.sort.length > 0) { - solrQuery.sort = query.sort.map(sortOption => { - const sortField = sortOption.field; - const sortOrder = sortOption.order === "desc" ? "desc" : "asc"; - return `${sortField} ${sortOrder}`; - }).join(', '); - } - - if (query && query.aggs && query.aggs.length > 0) { - let facets = query.aggs.map(agg => this._convertAggToFacet(agg)); - solrQuery.factes = facets.join(','); - } - - solrQuery.wt = "json" - - return solrQuery; - } - - _args2URL({ baseUrl, args }) { - const qParts = Object.keys(args).flatMap(k => { - const v = args[k]; - if (Array.isArray(v)) { - return v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`); - } - return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; - }); - - const qs = qParts.join("&"); - return `${baseUrl}?${qs}`; - } - - _convertAggToFacet(agg) { - const field = agg.field; - const name = agg.name; - const size = agg.size || 10; // default size if not specified - const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr - const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified - - return `facet.field={!key=${name}}${field}&f.${field}.facet.limit=${size}&f.${field}.facet.sort=${order} ${direction}`; - } - - _querySuccess(callback, error_callback) { - return function(data) { - if (data.hasOwnProperty("error")) { - error_callback(data); - return; - } - - var result = new es.Result({raw: data}); - callback(result); - } - } - - _queryError(callback) { - return function(data) { - if (callback) { - callback(data); - } else { - throw new Error(data); - } - } - } } // Result class for solr From 75eceea8338cf901ad193f4c55f24d47efa65cde Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Wed, 3 Jul 2024 13:58:59 +0530 Subject: [PATCH 07/21] Dead code removed --- src/edges.js | 53 ---------------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/src/edges.js b/src/edges.js index cef3fec..ebe4fb3 100644 --- a/src/edges.js +++ b/src/edges.js @@ -839,59 +839,6 @@ edges.es.SolrQueryAdapter = class extends edges.QueryAdapter { }; } -// Result class for solr -class SolrResult { - constructor(params) { - this.data = JSON.parse(params.raw); - } - - buckets(facet_name) { - if (this.data.facet_counts) { - if (this.data.facet_counts.facet_fields && this.data.facet_counts.facet_fields[facet_name]) { - return this._convertFacetToBuckets(this.data.facet_counts.facet_fields[facet_name]); - } else if (this.data.facet_counts.facet_queries && this.data.facet_counts.facet_queries[facet_name]) { - return this._convertFacetToBuckets(this.data.facet_counts.facet_queries[facet_name]); - } - } - return []; - } - - _convertFacetToBuckets(facet) { - let buckets = []; - for (let i = 0; i < facet.length; i += 2) { - buckets.push({ - key: facet[i], - doc_count: facet[i + 1] - }); - } - return buckets; - } - - aggregation(facet_name) { - return { - buckets: this.buckets(facet_name) - }; - } - - results() { - var res = []; - if (this.data.response && this.data.response.docs) { - for (var i = 0; i < this.data.response.docs.length; i++) { - res.push(this.data.response.docs[i]); - } - } - - return res; - } - - total() { - if (this.data.response && this.data.response.numFound !== undefined) { - return parseInt(this.data.response.numFound); - } - return false; - } -} - ////////////////////////////////////////////////////////////////// // utilities From b79c9b02f32518782b67ee354fc9289b8ca063c9 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Wed, 3 Jul 2024 13:59:20 +0530 Subject: [PATCH 08/21] Solr facet query is updated --- src/datasources/solr9x.js | 605 +------------------------------------- 1 file changed, 16 insertions(+), 589 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index e6dbf8c..8258053 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -2,14 +2,6 @@ if (!window.hasOwnProperty("es")) { es = {}} // request method to be used throughout. Set this before using the module if you want it different es.requestMethod = "get"; -es.distanceUnits = ["km", "mi", "miles", "in", "inch", "yd", "yards", "kilometers", "mm", "millimeters", "cm", "centimeters", "m", "meters"]; - -es.specialChars = ["\\", "+", "-", "=", "&&", "||", ">", "<", "!", "(", ")", "{", "}", "[", "]", "^", '"', "~", "*", "?", ":", "/"]; -es.characterPairs = ['"']; - -// the reserved special character set with * and " removed, so that users can do quote searches and wildcards -// if they want -es.specialCharsSubSet = ["\\", "+", "-", "=", "&&", "||", ">", "<", "!", "(", ")", "{", "}", "[", "]", "^", "~", "?", ":", "/"]; // add request headers (such as Auth) if you need to es.requestHeaders = false; @@ -105,18 +97,6 @@ es.Filter = class { parse() {} }; -es._classExtends = function(clazz, ref) { - if (clazz.__proto__ === null) { - return false; - } - if (clazz.__proto__ === ref) { - return true; - } - else { - return es._classExtends(clazz.__proto__, ref); - } -} - // Define the Query class es.Query = class { @@ -721,29 +701,6 @@ es.TermFilter = class extends es.Filter { } } -es.ExistsFilter = class extends es.Filter { - static type = "exists"; - - constructor(params) { - super(params); - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - return {exists: {field: this.field}}; - } - - parse(obj) { - if (obj.exists) { - obj = obj.exists; - } - this.field = obj.field; - } -} - es.TermsFilter = class extends es.Filter { static type = "terms"; @@ -836,304 +793,6 @@ es.TermsFilter = class extends es.Filter { } }; -es.BoolFilter = class extends es.Filter { - static type = "bool"; - - constructor(params) { - params["field"] = "_bool"; // Set a placeholder field for the filter - super(params); - - // For the moment, this implementation only supports must_not as it's all we need - this.mustNot = es.getParam(params.mustNot, []); - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - let obj = {bool: {}}; - if (this.mustNot.length > 0) { - let mustNots = []; - for (var i = 0; i < this.mustNot.length; i++) { - var m = this.mustNot[i]; - mustNots.push(m.objectify()); - } - obj.bool["must_not"] = mustNots; - } - return obj; - } - - parse(obj) { - if (obj.bool.must_not) { - for (var i = 0; i < obj.bool.must_not.length; i++) { - var type = Object.keys(obj.bool.must_not[i])[0]; - var fil = es.filterFactory(type, {raw: obj.bool.must_not[i]}); - if (fil) { - this.addMustNot(fil); - } - } - } - } - - addMustNot(filter) { - var existing = this.listMustNot(filter); - if (existing.length === 0) { - this.mustNot.push(filter); - } - } - - listMustNot(template) { - return this.listFilters({boolType: "must_not", template: template}); - } - - listFilters(params) { - var boolType = params.boolType || "must"; - var template = params.template || false; - - var bool = []; - if (boolType === "must") { - bool = this.aggs; // Using `this.aggs` assuming it holds the list of aggregations in Solr context - } else if (boolType === "should") { - bool = []; // Adjust as per your Solr implementation for 'should' clauses - } else if (boolType === "must_not") { - bool = this.mustNot; - } - - if (!template) { - return bool; - } - var l = []; - for (var i = 0; i < bool.length; i++) { - var m = bool[i]; - if (m.matches(template)) { - l.push(m); - } - } - return l; - } -} - -es.RangeFilter = class extends es.Filter { - static type = "range"; - - constructor(params) { - super(params); - - // Field handled by superclass - this.lt = es.getParam(params.lt, false); - this.lte = es.getParam(params.lte, false); - this.gte = es.getParam(params.gte, false); - this.format = es.getParam(params.format, false); - - // Normalize the values to strings - if (this.lt !== false) { - this.lt = this.lt.toString(); - } - if (this.lte !== false) { - this.lte = this.lte.toString(); - } - if (this.gte !== false) { - this.gte = this.gte.toString(); - } - - if (params.raw) { - this.parse(params.raw); - } - } - - matches(other) { - // Ask the parent object first - let pm = super.matches(other); - if (!pm) { - return false; - } - - // Ranges (if set) must match - if (other.lt !== undefined) { - if (other.lt.toString() !== this.lt) { - return false; - } - } - if (other.lte !== undefined) { - if (other.lte.toString() !== this.lte) { - return false; - } - } - if (other.gte !== undefined) { - if (other.gte.toString() !== this.gte) { - return false; - } - } - - if (other.format !== undefined) { - if (other.format !== this.format) { - return false; - } - } - - return true; - } - - objectify() { - var obj = {range: {}}; - obj.range[this.field] = {}; - if (this.lte !== false) { - obj.range[this.field]["lte"] = this.lte; - } - if (this.lt !== false && this.lte === false) { - obj.range[this.field]["lt"] = this.lt; - } - if (this.gte !== false) { - obj.range[this.field]["gte"] = this.gte; - } - if (this.format !== false) { - obj.range[this.field]["format"] = this.format; - } - return obj; - } - - parse(obj) { - if (obj.range) { - obj = obj.range; - } - this.field = Object.keys(obj)[0]; - if (obj[this.field].lte !== undefined) { - this.lte = obj[this.field].lte.toString(); - } - if (obj[this.field].lt !== undefined) { - this.lt = obj[this.field].lt.toString(); - } - if (obj[this.field].gte !== undefined) { - this.gte = obj[this.field].gte.toString(); - } - if (obj[this.field].format !== undefined) { - this.format = obj[this.field].format; - } - } -} - -es.GeoDistanceRangeFilter = class extends es.Filter { - static type = "geo_distance_range"; - - constructor(params) { - super(params); - - // Field is handled by superclass - this.lt = params.lt || false; - this.gte = params.gte || false; - this.lat = params.lat || false; - this.lon = params.lon || false; - this.unit = params.unit || "m"; - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var obj = {geo_distance_range: {}}; - obj.geo_distance_range[this.field] = {lat: this.lat, lon: this.lon}; - if (this.lt) { - obj.geo_distance_range[this.field]["lt"] = this.lt + this.unit; - } - if (this.gte) { - obj.geo_distance_range[this.field]["gte"] = this.gte + this.unit; - } - return obj; - } - - parse(obj) { - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } - - function splitUnits(str) { - var unit = false; - for (var i = 0; i < es.distanceUnits.length; i++) { - var cu = es.distanceUnits[i]; - if (endsWith(str, cu)) { - str = str.substring(0, str.length - cu.length); - unit = cu; // Solr uses full unit name directly - break; - } - } - return [str, unit]; - } - - if (obj.geo_distance_range) { - obj = obj.geo_distance_range; - } - this.field = Object.keys(obj)[0]; - this.lat = obj[this.field].lat; - this.lon = obj[this.field].lon; - - var lt = obj[this.field].lt; - var gte = obj[this.field].gte; - - if (lt) { - lt = lt.trim(); - var parts = splitUnits(lt); - this.lt = parts[0]; - this.unit = parts[1]; - } - - if (gte) { - gte = gte.trim(); - var parts = splitUnits(gte); - this.gte = parts[0]; - this.unit = parts[1]; - } - } -} - -es.GeoBoundingBoxFilter = class extends es.Filter { - static type = "geo_bounding_box"; - - constructor(params) { - super(params); - this.top_left = params.top_left || false; - this.bottom_right = params.bottom_right || false; - - if (params.raw) { - this.parse(params.raw); - } - } - - matches(other) { - // Ask the parent object first - let pm = super.matches(other); - if (!pm) { - return false; - } - if (other.top_left && other.top_left !== this.top_left) { - return false; - } - if (other.bottom_right && other.bottom_right !== this.bottom_right) { - return false; - } - return true; - } - - objectify() { - var obj = {geo_bounding_box: {}}; - obj.geo_bounding_box[this.field] = { - top_left: this.top_left, - bottom_right: this.bottom_right - }; - return obj; - } - - parse(obj) { - if (obj.geo_bounding_box) { - obj = obj.geo_bounding_box; - } - this.field = Object.keys(obj)[0]; - this.top_left = obj[this.field].top_left; - this.bottom_right = obj[this.field].bottom_right; - } -} - // Aggregation extended classes starts here es.TermsAggregation = class extends es.Aggregation { @@ -1208,233 +867,7 @@ es.TermsAggregation = class extends es.Aggregation { } }; -es.CardinalityAggregation = class extends es.Aggregation { - static type = "cardinality"; - - constructor(params) { - super(params); - this.field = es.getParam(params.field, false); - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = {field: this.field}; - return this._make_aggregation(es.CardinalityAggregation.type, body); - } - - parse(obj) { - var body = this._parse_wrapper(obj, es.CardinalityAggregation.type); - this.field = body.field; - } -} - -es.RangeAggregation = class extends es.Aggregation { - static type = "range"; - - constructor(params) { - super(params); - this.field = params.field || false; - this.ranges = params.ranges || []; - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = {field: this.field, ranges: this.ranges}; - return this._make_aggregation(es.RangeAggregation.type, body); - } - parse(obj) { - var body = this._parse_wrapper(obj, es.RangeAggregation.type); - this.field = body.field; - this.ranges = body.ranges; - } -} - -es.GeoDistanceAggregation = class extends es.Aggregation { - static type = "geo_distance"; - - constructor(params) { - super(params); - this.field = params.field || false; - this.lat = params.lat || false; - this.lon = params.lon || false; - this.unit = params.unit || "m"; - this.distance_type = params.distance_type || "arc"; // Solr uses "arc" instead of "sloppy_arc" - this.ranges = params.ranges || []; - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = { - field: this.field, - origin: `${this.lat},${this.lon}`, // Solr uses lat,lon as a string - unit: this.unit, - distance_type: this.distance_type, - ranges: this.ranges - }; - return this._make_aggregation(es.GeoDistanceAggregation.type, body); - } - - parse(obj) { - var body = this._parse_wrapper(obj, es.GeoDistanceAggregation.type); - this.field = body.field; - - var origin = body.origin.split(","); - if (origin.length === 2) { - this.lat = parseFloat(origin[0]); - this.lon = parseFloat(origin[1]); - } - - if (body.unit) { - this.unit = body.unit; - } - - if (body.distance_type) { - this.distance_type = body.distance_type; - } - - this.ranges = body.ranges; - } -} - -es.GeohashGridAggregation = class extends es.Aggregation { - static type = "geohash_grid"; - - constructor(params) { - super(params); - this.field = params.field || false; - this.precision = params.precision || 3; - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = { - field: this.field, - precision: this.precision - }; - return this._make_aggregation(es.GeohashGridAggregation.type, body); - } - - parse(obj) { - var body = this._parse_wrapper(obj, es.GeohashGridAggregation.type); - this.field = body.field; - this.precision = body.precision; - } -} - -es.StatsAggregation = class extends es.Aggregation { - static type = "stats"; - - constructor(params) { - super(params); - this.field = params.field || false; - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = {field: this.field}; - return this._make_aggregation(es.StatsAggregation.type, body); - } - - parse(obj) { - var body = this._parse_wrapper(obj, es.StatsAggregation.type); - this.field = body.field; - } -} - -es.SumAggregation = class extends es.Aggregation { - static type = "sum"; - - constructor(params) { - super(params); - this.field = params.field || false; - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = {field: this.field}; - return this._make_aggregation(es.SumAggregation.type, body); - } - - parse(obj) { - var body = this._parse_wrapper(obj, es.SumAggregation.type); - this.field = body.field; - } -} - -es.DateHistogramAggregation = class extends es.Aggregation { - static type = "date_histogram"; - - constructor(params) { - super(params); - this.field = params.field || false; - this.interval = params.interval || "month"; - this.format = params.format || false; - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = {field: this.field, interval: this.interval}; - if (this.format) { - body["format"] = this.format; - } - return this._make_aggregation(es.DateHistogramAggregation.type, body); - } - - parse(obj) { - var body = this._parse_wrapper(obj, es.DateHistogramAggregation.type); - this.field = body.field; - if (body.interval) { - this.interval = body.interval; - } - if (body.format) { - this.format = body.format; - } - } -} - -es.FiltersAggregation = class extends es.Aggregation { - static type = "filters"; - - constructor(params) { - super(params); - this.filters = params.filters || {}; - - if (params.raw) { - this.parse(params.raw); - } - } - - objectify() { - var body = {filters: this.filters}; - return this._make_aggregation(es.FiltersAggregation.type, body); - } - - parse(obj) { - var body = this._parse_wrapper(obj, es.FiltersAggregation.type); - this.filters = body.filters; - } -} es.doQuery = (params) => { const { success, error, complete, search_url, query, datatype } = params; @@ -1443,7 +876,7 @@ es.doQuery = (params) => { const searchUrl = search_url; // Generate the Solr query URL const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); - + console.log("URK" , fullUrl) var error_callback = es.queryError(error); var success_callback = es.querySuccess(success, error_callback); @@ -1606,13 +1039,14 @@ function _es2solr({ query }) { } if (query && query.aggs && query.aggs.length > 0) { - let facetsFields = query.aggs.map(agg => this._convertAggFieldToFacetField(agg)); + solrQuery.facets = query.aggs.map(agg => agg.field); query.aggs.forEach(agg => { - _convertAggLimitToFacetLimit(agg, solrQuery); - _convertAggSortToFacetSort(agg, solrQuery); + _covertAffLimitAndSortToFacet(agg , solrQuery); }); + solrQuery.facet = true - solrQuery["facet.field"] = facetsFields.join(",") + + console.log("solrQuery" , solrQuery.facets) } if(query && query.must && query.must.length > 0) { @@ -1642,7 +1076,12 @@ function _args2URL({ baseUrl, args }) { const qParts = Object.keys(args).flatMap(k => { const v = args[k]; if (Array.isArray(v)) { - return v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`); + + if (k === 'facets') { + return v.map(item => `facet.field=${encodeURIComponent(item)}`); + } else { + return v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`); + } } return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; }); @@ -1651,25 +1090,13 @@ function _args2URL({ baseUrl, args }) { return `${baseUrl}?${qs}`; } -function _convertAggFieldToFacetField(agg) { - const field = agg.field; - const name = agg.name; - - return `{!key=${name}}${field}`; -} - -function _convertAggLimitToFacetLimit(agg , solrQuery) { - const size = agg.size || 10; // default size if not specified +function _covertAffLimitAndSortToFacet(agg , solrQuery) { const field = agg.field; - - solrQuery[`f.${field}.facet.limit`] = size -} - -function _convertAggSortToFacetSort(agg , solrQuery) { + const size = agg.size || 10; // default size if not specified const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified - const field = agg.field; - + + solrQuery[`f.${field}.facet.limit`] = size solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}` } From 40b1e006babae3b3f415dd61a511740bc6063e26 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Tue, 6 Aug 2024 13:28:43 +0530 Subject: [PATCH 09/21] Bug inside refine and term is handled if the terms inside filters is missing --- src/renderers/bs3/RefiningANDTermSelector.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderers/bs3/RefiningANDTermSelector.js b/src/renderers/bs3/RefiningANDTermSelector.js index 9bd2249..8db7fd7 100644 --- a/src/renderers/bs3/RefiningANDTermSelector.js +++ b/src/renderers/bs3/RefiningANDTermSelector.js @@ -85,7 +85,9 @@ edges.renderers.bs3.RefiningANDTermSelector = class extends edges.Renderer { // get the terms of the filters that have already been set var filterTerms = []; for (var i = 0; i < ts.filters.length; i++) { - filterTerms.push(ts.filters[i].term.toString()); + if(ts.filters[i].term){ + filterTerms.push(ts.filters[i].term.toString()); + } } // render each value, if it is not also a filter that has been set From c6f0d130247c55c56eca3e100e0b75404efae0f6 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Tue, 6 Aug 2024 13:49:16 +0530 Subject: [PATCH 10/21] solr must query is corrected --- src/datasources/solr9x.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index 8258053..6aac745 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -261,6 +261,7 @@ es.Query = class { // Filter Methods addMust(filter) { + console.log("Add must" , filter) if (!this.listMust().some(existingFilter => { return Object.keys(filter).every(key => existingFilter[key] === filter[key]); })) { @@ -876,7 +877,6 @@ es.doQuery = (params) => { const searchUrl = search_url; // Generate the Solr query URL const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); - console.log("URK" , fullUrl) var error_callback = es.queryError(error); var success_callback = es.querySuccess(success, error_callback); @@ -1045,13 +1045,15 @@ function _es2solr({ query }) { }); solrQuery.facet = true - - console.log("solrQuery" , solrQuery.facets) } if(query && query.must && query.must.length > 0) { query.must.forEach(mustQuery => { - solrQuery.q = `${mustQuery.field}:${mustQuery.value}` + const term = mustQuery.term; + const field = Object.keys(term)[0]; + const value = term[field]; + + solrQuery.q = `${field}:${value}`; }); } From f8a20dac13f624ae8d49c679ee2f08b43eb1a0a1 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Tue, 24 Sep 2024 13:02:49 +0530 Subject: [PATCH 11/21] Solr code modified and added support for multiple range and multiple queries --- src/datasources/solr9x.js | 75 ++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index 6aac745..ef3fe0c 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -116,6 +116,8 @@ es.Query = class { this.mustNot = es.getParam(params.mustNot, []); this.should = es.getParam(params.should, []); this.minimumShouldMatch = es.getParam(params.minimumShouldMatch, false); + this.query = es.getParam(params.query, {}); + this.queryStrings = es.getParam(params.queryStrings, []); // Defaults from properties set through their setters this.queryString = false; @@ -261,7 +263,6 @@ es.Query = class { // Filter Methods addMust(filter) { - console.log("Add must" , filter) if (!this.listMust().some(existingFilter => { return Object.keys(filter).every(key => existingFilter[key] === filter[key]); })) { @@ -872,7 +873,7 @@ es.TermsAggregation = class extends es.Aggregation { es.doQuery = (params) => { const { success, error, complete, search_url, query, datatype } = params; - + console.log("Query" , query) const solrArgs = this._es2solr({ query : query }); const searchUrl = search_url; // Generate the Solr query URL @@ -972,18 +973,22 @@ function _es2solr({ query }) { let solrFacets = [] // Handle the query part - if (query.query) { + if (Object.entries(query.query).length > 0) { const queryPart = query.query; if (queryPart.match) { const field = Object.keys(queryPart.match)[0]; const value = queryPart.match[field]; solrQuery.q = `${field}:${value}`; } else if (queryPart.range) { - const field = Object.keys(queryPart.range)[0]; - const range = queryPart.range[field]; - const rangeQuery = `${field}:[${range.gte || '*'} TO ${range.lte || '*'}]`; - solrQuery.fq = rangeQuery; - } else if (queryPart.match_all) { + const fields = Object.keys(queryPart.range); + const rangeQueries = fields.map(field => { + const range = queryPart.range[field]; + return `${field}:[${range.gte || '*'} TO ${range.lte || '*'}]`; + }); + + // Join the range queries with OR if there are multiple + solrQuery.fq = rangeQueries.length > 1 ? `(${rangeQueries.join(' OR ')})` : rangeQueries[0]; + } else if (queryPart.match_all) { solrQuery.q = `*:*`; } } else { @@ -1016,6 +1021,33 @@ function _es2solr({ query }) { }).join(', '); } + if (query.queryStrings && query.queryStrings.length > 0) { + query.queryStrings.forEach((query, queryIndex) => { + const esQueryString = query.queryString; + const fields = query.fields; + + if (typeof esQueryString == 'boolean') { + throw new Error('Search string needs to be a string, got boolean'); + } + + if (esQueryString !== "" && Array.isArray(fields) && fields.length > 0) { + fields.forEach((fieldConfig, index) => { + const { field, operator = "OR" } = fieldConfig; + // Clearning solr query only in case of *:* + if(solrQuery.q == "*:*") { + solrQuery.q = "" + } + + if (solrQuery.q) { + solrQuery.q += ` ${operator} ${field}:${esQueryString}`; + } else { + solrQuery.q = `${field}:${esQueryString}`; + } + }); + } + }); + } + if (query.queryString && query.queryString.queryString) { const esQueryString = query.queryString.queryString; const searchField = query.queryString.defaultField; @@ -1053,24 +1085,43 @@ function _es2solr({ query }) { const field = Object.keys(term)[0]; const value = term[field]; - solrQuery.q = `${field}:${value}`; + if(solrQuery.q == "*:*") { + solrQuery.q = "" + } + + if(solrQuery.q) { + solrQuery.q += ` AND ${field}:${value}`; + } else { + solrQuery.q = `${field}:${value}`; + } + + console.log("Query" , solrQuery.q) }); } if(query && query.mustNot && query.mustNot.length > 0) { query.mustNot.forEach(mustNotq => { - solrQuery.q = `-${mustNotq.field}:${mustNotq.value}` + const term = mustQuery.term; + const field = Object.keys(term)[0]; + const value = term[field]; + + solrQuery.q = `-${field}:${value}` }); } if(query && query.should && query.should.length > 0) { - query.should.forEach(shouldQ => { - solrQuery.q = `(${shouldQ.field}:${shouldQ.value})^1.0` + query.should.forEach((shouldQ , index) => { + const term = mustQuery.term; + const field = Object.keys(term)[0]; + const value = term[field]; + + solrQuery.q = `${field}:${value}^1.0`; }); } solrQuery.wt = "json" + console.log("Returing" , solrQuery.q) return solrQuery; } From 340b3b46e6be06f5b45d14541bc475eb0ab184a5 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Tue, 24 Sep 2024 13:21:20 +0530 Subject: [PATCH 12/21] Debug message removed --- src/datasources/solr9x.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index ef3fe0c..4b5390e 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -873,7 +873,7 @@ es.TermsAggregation = class extends es.Aggregation { es.doQuery = (params) => { const { success, error, complete, search_url, query, datatype } = params; - console.log("Query" , query) + const solrArgs = this._es2solr({ query : query }); const searchUrl = search_url; // Generate the Solr query URL @@ -1094,8 +1094,6 @@ function _es2solr({ query }) { } else { solrQuery.q = `${field}:${value}`; } - - console.log("Query" , solrQuery.q) }); } @@ -1121,7 +1119,6 @@ function _es2solr({ query }) { solrQuery.wt = "json" - console.log("Returing" , solrQuery.q) return solrQuery; } From 3ed689c7c0449f1b6ce01ed3a3c32f1d57cb37aa Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Tue, 1 Oct 2024 11:06:10 +0530 Subject: [PATCH 13/21] Changes in the query part for multiple field --- src/datasources/solr9x.js | 77 ++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index 4b5390e..1fec271 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -1023,29 +1023,44 @@ function _es2solr({ query }) { if (query.queryStrings && query.queryStrings.length > 0) { query.queryStrings.forEach((query, queryIndex) => { - const esQueryString = query.queryString; - const fields = query.fields; - - if (typeof esQueryString == 'boolean') { - throw new Error('Search string needs to be a string, got boolean'); + const esQueryString = query.queryString; + const fields = query.fields; + + if (typeof esQueryString == 'boolean') { + throw new Error('Search string needs to be a string, got boolean'); + } + + if (esQueryString !== "" && Array.isArray(fields) && fields.length > 0) { + let queryPart = ""; // To hold the query part for this set of fields + + fields.forEach((fieldConfig, index) => { + const { field, operator = "OR" } = fieldConfig; + + // Cleaning solr query only in case of *:* + if (solrQuery.q == "*:*") { + solrQuery.q = ""; + } + + if (queryPart) { + queryPart += ` ${operator} ${field}:${esQueryString}`; + } else { + queryPart = `${field}:${esQueryString}`; + } + }); + + // If there are multiple fields, wrap the query in parentheses + if (fields.length > 1) { + queryPart = `(${queryPart})`; } - - if (esQueryString !== "" && Array.isArray(fields) && fields.length > 0) { - fields.forEach((fieldConfig, index) => { - const { field, operator = "OR" } = fieldConfig; - // Clearning solr query only in case of *:* - if(solrQuery.q == "*:*") { - solrQuery.q = "" - } - if (solrQuery.q) { - solrQuery.q += ` ${operator} ${field}:${esQueryString}`; - } else { - solrQuery.q = `${field}:${esQueryString}`; - } - }); + // Append to the existing solrQuery.q + if (solrQuery.q) { + solrQuery.q += ` AND ${queryPart}`; + } else { + solrQuery.q = queryPart; } - }); + } + }); } if (query.queryString && query.queryString.queryString) { @@ -1072,9 +1087,9 @@ function _es2solr({ query }) { if (query && query.aggs && query.aggs.length > 0) { solrQuery.facets = query.aggs.map(agg => agg.field); - query.aggs.forEach(agg => { - _covertAffLimitAndSortToFacet(agg , solrQuery); - }); + // query.aggs.forEach(agg => { + // _covertAffLimitAndSortToFacet(agg , solrQuery); + // }); solrQuery.facet = true } @@ -1128,7 +1143,8 @@ function _args2URL({ baseUrl, args }) { if (Array.isArray(v)) { if (k === 'facets') { - return v.map(item => `facet.field=${encodeURIComponent(item)}`); + const result = v[0].split(',').map(item => `facet.field=${encodeURIComponent(item)}`).join('&'); + return result } else { return v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`); } @@ -1137,17 +1153,20 @@ function _args2URL({ baseUrl, args }) { }); const qs = qParts.join("&"); - return `${baseUrl}?${qs}`; + return `${baseUrl}?${qs}`; + } function _covertAffLimitAndSortToFacet(agg , solrQuery) { - const field = agg.field; + const fields = agg.field.split(","); const size = agg.size || 10; // default size if not specified const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified - - solrQuery[`f.${field}.facet.limit`] = size - solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}` + + fields.forEach(field => { + solrQuery[`f.${field}.facet.limit`] = size + solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}` + }); } From 90fa0c2fd1eb989d6b1caf927ec33464214e336a Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Tue, 8 Oct 2024 19:58:12 +0530 Subject: [PATCH 14/21] Sorting error is fixed --- src/datasources/solr9x.js | 2128 +++++++++++++++++++------------------ 1 file changed, 1101 insertions(+), 1027 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index 1fec271..e324175 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -1,4 +1,6 @@ -if (!window.hasOwnProperty("es")) { es = {}} +if (!window.hasOwnProperty("es")) { + es = {}; +} // request method to be used throughout. Set this before using the module if you want it different es.requestMethod = "get"; @@ -8,1168 +10,1240 @@ es.requestHeaders = false; // Base classes es.Aggregation = class { - static type = "aggregation"; - - constructor(params) { - this.name = params.name; - this.aggs = params.aggs || []; - } - - addAggregation(agg) { - for (var i = 0; i < this.aggs.length; i++) { - if (this.aggs[i].name === agg.name) { - return; - } - } - this.aggs.push(agg); - } - - removeAggregation() {} - clearAggregations() {} - - // for use by sub-classes, for their convenience in rendering - // the overall structure of the aggregation to an object - _make_aggregation(type, body) { - var obj = {}; - obj[this.name] = {}; - obj[this.name][type] = body; - - if (this.aggs.length > 0) { - obj[this.name]["aggs"] = {}; - for (var i = 0; i < this.aggs.length; i++) { - $.extend(obj[this.name]["aggs"], this.aggs[i].objectify()); - } - } - - return obj; - } - - _parse_wrapper(obj, type) { - this.name = Object.keys(obj)[0]; - var body = obj[this.name][type]; - - var aggs = obj[this.name].aggs ? obj[this.name].aggs : obj[this.name].aggregations; - if (aggs) { - var anames = Object.keys(aggs); - for (var i = 0; i < anames.length; i++) { - var name = anames[i]; - var agg = aggs[anames[i]]; - var subtype = Object.keys(agg)[0]; - var raw = {}; - raw[name] = agg; - var oa = es.aggregationFactory(subtype, {raw: raw}); - if (oa) { - this.addAggregation(oa); - } - } - } - - return body; - } + static type = "aggregation"; + + constructor(params) { + this.name = params.name; + this.aggs = params.aggs || []; + } + + addAggregation(agg) { + for (var i = 0; i < this.aggs.length; i++) { + if (this.aggs[i].name === agg.name) { + return; + } + } + this.aggs.push(agg); + } + + removeAggregation() {} + clearAggregations() {} + + // for use by sub-classes, for their convenience in rendering + // the overall structure of the aggregation to an object + _make_aggregation(type, body) { + var obj = {}; + obj[this.name] = {}; + obj[this.name][type] = body; + + if (this.aggs.length > 0) { + obj[this.name]["aggs"] = {}; + for (var i = 0; i < this.aggs.length; i++) { + $.extend(obj[this.name]["aggs"], this.aggs[i].objectify()); + } + } + + return obj; + } + + _parse_wrapper(obj, type) { + this.name = Object.keys(obj)[0]; + var body = obj[this.name][type]; + + var aggs = obj[this.name].aggs + ? obj[this.name].aggs + : obj[this.name].aggregations; + if (aggs) { + var anames = Object.keys(aggs); + for (var i = 0; i < anames.length; i++) { + var name = anames[i]; + var agg = aggs[anames[i]]; + var subtype = Object.keys(agg)[0]; + var raw = {}; + raw[name] = agg; + var oa = es.aggregationFactory(subtype, { raw: raw }); + if (oa) { + this.addAggregation(oa); + } + } + } + + return body; + } }; - es.Filter = class { - static type = "filter"; - - constructor(params) { - this.field = params.field; - } - - matches(other) { - return this._baseMatch(other); - } - - _baseMatch(other) { - // type must match - if (other.type !== this.type) { - return false; - } - // field (if set) must match - if (other.field && other.field !== this.field) { - return false; - } - // otherwise this matches - return true; - } - - objectify() {} - parse() {} -}; - - -// Define the Query class -es.Query = class { - constructor(params) { - if (!params) { params = {}; } - - // Properties initialization - this.filtered = false; // no longer present in ES 5.x+ - this.trackTotalHits = true; // FIXME: hard code this for now - - // Initialize with default values or from params - this.size = es.getParam(params.size, false); - this.from = es.getParam(params.from, false); - this.fields = es.getParam(params.fields, []); - this.aggs = es.getParam(params.aggs, []); - this.must = es.getParam(params.must, []); - this.mustNot = es.getParam(params.mustNot, []); - this.should = es.getParam(params.should, []); - this.minimumShouldMatch = es.getParam(params.minimumShouldMatch, false); - this.query = es.getParam(params.query, {}); - this.queryStrings = es.getParam(params.queryStrings, []); - - // Defaults from properties set through their setters - this.queryString = false; - this.sort = []; - - // Properties awaiting implementation - this.source = es.getParam(params.source, false); - this.partialFields = es.getParam(params.partialFields, false); // using partialFields instead of partialField - this.scriptFields = es.getParam(params.scriptFields, false); - - // For older ES versions, may not be implemented - this.facets = es.getParam(params.facets, []); - - // Final part of construction - set dynamic properties via their setters - if (params.queryString) { - this.setQueryString(params.queryString); - } + static type = "filter"; - if (params.sort) { - this.setSortBy(params.sort); - } - - // Parse raw query if provided - if (params.raw) { - this.parse(params.raw); - } - } - - // Getters and Setters - getSize() { - return this.size !== undefined && this.size !== false ? this.size : 10; - } - - getFrom() { - return this.from || 0; - } - - addField(field) { - if (!this.fields.includes(field)) { - this.fields.push(field); - } - } - - setQueryString(params) { - let qs = params; - if (!(params instanceof es.QueryString)) { - if ($.isPlainObject(params)) { - qs = new es.QueryString(params); - } else { - qs = new es.QueryString({ queryString: params }); - } - } - this.queryString = qs; - } - - getQueryString() { - return this.queryString; - } - - removeQueryString() { - this.queryString = false; - } - - setSortBy(params) { - this.sort = []; - let sorts = Array.isArray(params) ? params : [params]; - sorts.forEach(sort => this.addSortBy(sort)); - } - - addSortBy(params) { - let sort = params instanceof es.Sort ? params : new es.Sort(params); - if (!this.sort.some(existingSort => existingSort.field === sort.field)) { - this.sort.push(sort); - } - } - - prependSortBy(params) { - let sort = params instanceof es.Sort ? params : new es.Sort(params); - this.removeSortBy(sort); - this.sort.unshift(sort); - } - - removeSortBy(params) { - let sort = params instanceof es.Sort ? params : new es.Sort(params); - this.sort = this.sort.filter(existingSort => existingSort.field !== sort.field); - } - - getSortBy() { - return this.sort; - } + constructor(params) { + this.field = params.field; + } - setSourceFilters(params) { - this.source = this.source || { include: [], exclude: [] }; - if (params.include) { - this.source.include = params.include; - } - if (params.exclude) { - this.source.exclude = params.exclude; - } - } + matches(other) { + return this._baseMatch(other); + } - addSourceFilters(params) { - this.source = this.source || { include: [], exclude: [] }; - if (params.include) { - this.source.include.push(...params.include); - } - if (params.exclude) { - this.source.exclude.push(...params.exclude); - } + _baseMatch(other) { + // type must match + if (other.type !== this.type) { + return false; } - - getSourceIncludes() { - return this.source && this.source.include ? this.source.include : []; + // field (if set) must match + if (other.field && other.field !== this.field) { + return false; } + // otherwise this matches + return true; + } - getSourceExcludes() { - return this.source && this.source.exclude ? this.source.exclude : []; - } + objectify() {} + parse() {} +}; - // Aggregation Methods - getAggregation(params) { - return this.aggs.find(agg => agg.name === params.name); - } +es.Sort = class { + constructor(params) { + this.field = params.field || "score"; + this.order = params.order || "desc"; // Solr uses 'score' instead of '_score' for default relevance sorting - addAggregation(agg, overwrite) { - if (overwrite) { - this.removeAggregation(agg.name); - } - this.aggs.push(agg); + if (params.raw) { + this.parse(params.raw); } + } - removeAggregation(name) { - this.aggs = this.aggs.filter(agg => agg.name !== name); - } + objectify() { + return `${this.field} ${this.order}`; // Solr uses 'field order' format as a string + } - clearAggregations() { - this.aggs = []; - } + parse(str) { + const parts = str.split(" "); + this.field = parts[0]; + this.order = parts[1] || "desc"; // Default to 'desc' if no order is provided + } +}; - listAggregations() { - return this.aggs; - } +// Define the Query class +es.Query = class { + constructor(params) { + if (!params) { + params = {}; + } + + // Properties initialization + this.filtered = false; // no longer present in ES 5.x+ + this.trackTotalHits = true; // FIXME: hard code this for now + + // Initialize with default values or from params + this.size = es.getParam(params.size, false); + this.from = es.getParam(params.from, false); + this.fields = es.getParam(params.fields, []); + this.aggs = es.getParam(params.aggs, []); + this.must = es.getParam(params.must, []); + this.mustNot = es.getParam(params.mustNot, []); + this.should = es.getParam(params.should, []); + this.minimumShouldMatch = es.getParam(params.minimumShouldMatch, false); + this.query = es.getParam(params.query, {}); + this.queryStrings = es.getParam(params.queryStrings, []); + + // Defaults from properties set through their setters + this.queryString = false; + this.sort = []; + + // Properties awaiting implementation + this.source = es.getParam(params.source, false); + this.partialFields = es.getParam(params.partialFields, false); // using partialFields instead of partialField + this.scriptFields = es.getParam(params.scriptFields, false); + + // For older ES versions, may not be implemented + this.facets = es.getParam(params.facets, []); - // Filter Methods - addMust(filter) { - if (!this.listMust().some(existingFilter => { - return Object.keys(filter).every(key => existingFilter[key] === filter[key]); - })) { - this.must.push(filter); - } + // Final part of construction - set dynamic properties via their setters + if (params.queryString) { + this.setQueryString(params.queryString); } - listMust() { - return this.must; + if (params.sort) { + this.setSortBy(params.sort); } - removeMust(template) { - let removedCount = 0; - this.must = this.must.filter(filter => { - - // Check if filter values match the template values - const matches = Object.keys(template).every(key => filter[key] === template[key]); - if (matches) { - removedCount++; - } - return !matches; - }); - return removedCount; - } - clearMust() { - this.must = []; + // Parse raw query if provided + if (params.raw) { + this.parse(params.raw); } + } + + // Getters and Setters + getSize() { + return this.size !== undefined && this.size !== false ? this.size : 10; + } - addMustNot(filter) { - if (!this.listMustNot().some(existingFilter => { - return Object.keys(filter).every(key => existingFilter[key] === filter[key]); - })) { - this.mustNot.push(filter); - } - } + getFrom() { + return this.from || 0; + } - listMustNot() { - return this.mustNot; + addField(field) { + if (!this.fields.includes(field)) { + this.fields.push(field); } + } - removeMustNot(template) { - let removedCount = 0; - this.mustNot = this.mustNot.filter(filter => { - const matches = Object.keys(template).every(key => filter[key] === template[key]); - if (matches) { - removedCount++; - } - return !matches; - }); - return removedCount; + setQueryString(params) { + let qs = params; + if (!(params instanceof es.QueryString)) { + if ($.isPlainObject(params)) { + qs = new es.QueryString(params); + } else { + qs = new es.QueryString({ queryString: params }); + } + } + this.queryString = qs; + } + + getQueryString() { + return this.queryString; + } + + removeQueryString() { + this.queryString = false; + } + + setSortBy(params) { + this.sort = []; + let sorts = Array.isArray(params) ? params : [params]; + sorts.forEach((sort) => this.addSortBy(sort)); + } + + addSortBy(params) { + let sort = params instanceof es.Sort ? params : new es.Sort(params); + if (!this.sort.some((existingSort) => existingSort.field === sort.field)) { + this.sort.push(sort); + } + } + + prependSortBy(params) { + let sort = params instanceof es.Sort ? params : new es.Sort(params); + this.removeSortBy(sort); + this.sort.unshift(sort); + } + + removeSortBy(params) { + let sort = params instanceof es.Sort ? params : new es.Sort(params); + this.sort = this.sort.filter( + (existingSort) => existingSort.field !== sort.field + ); + } + + getSortBy() { + return this.sort; + } + + setSourceFilters(params) { + this.source = this.source || { include: [], exclude: [] }; + if (params.include) { + this.source.include = params.include; + } + if (params.exclude) { + this.source.exclude = params.exclude; + } + } + + addSourceFilters(params) { + this.source = this.source || { include: [], exclude: [] }; + if (params.include) { + this.source.include.push(...params.include); + } + if (params.exclude) { + this.source.exclude.push(...params.exclude); + } + } + + getSourceIncludes() { + return this.source && this.source.include ? this.source.include : []; + } + + getSourceExcludes() { + return this.source && this.source.exclude ? this.source.exclude : []; + } + + // Aggregation Methods + getAggregation(params) { + return this.aggs.find((agg) => agg.name === params.name); + } + + addAggregation(agg, overwrite) { + if (overwrite) { + this.removeAggregation(agg.name); + } + this.aggs.push(agg); + } + + removeAggregation(name) { + this.aggs = this.aggs.filter((agg) => agg.name !== name); + } + + clearAggregations() { + this.aggs = []; + } + + listAggregations() { + return this.aggs; + } + + // Filter Methods + addMust(filter) { + if ( + !this.listMust().some((existingFilter) => { + return Object.keys(filter).every( + (key) => existingFilter[key] === filter[key] + ); + }) + ) { + this.must.push(filter); + } + } + + listMust() { + return this.must; + } + + removeMust(template) { + let removedCount = 0; + this.must = this.must.filter((filter) => { + // Check if filter values match the template values + const matches = Object.keys(template).every( + (key) => filter[key] === template[key] + ); + if (matches) { + removedCount++; + } + return !matches; + }); + return removedCount; + } + clearMust() { + this.must = []; + } + + addMustNot(filter) { + if ( + !this.listMustNot().some((existingFilter) => { + return Object.keys(filter).every( + (key) => existingFilter[key] === filter[key] + ); + }) + ) { + this.mustNot.push(filter); + } + } + + listMustNot() { + return this.mustNot; + } + + removeMustNot(template) { + let removedCount = 0; + this.mustNot = this.mustNot.filter((filter) => { + const matches = Object.keys(template).every( + (key) => filter[key] === template[key] + ); + if (matches) { + removedCount++; + } + return !matches; + }); + return removedCount; + } + + clearMustNot() { + this.mustNot = []; + } + + addShould(filter) { + if ( + !this.listShould().some((existingFilter) => { + return Object.keys(filter).every( + (key) => existingFilter[key] === filter[key] + ); + }) + ) { + this.should.push(filter); + } + } + + listShould() { + return this.should; + } + + removeShould(template) { + let removedCount = 0; + this.should = this.should.filter((filter) => { + const matches = Object.keys(template).every( + (key) => filter[key] === template[key] + ); + if (matches) { + removedCount++; + } + return !matches; + }); + return removedCount; + } + + clearShould() { + this.should = []; + } + + // Interrogative Methods + hasFilters() { + return ( + this.must.length > 0 || this.should.length > 0 || this.mustNot.length > 0 + ); + } + + listFilters(params) { + const { boolType, template } = params; + const matchesTemplate = (filter) => { + return Object.keys(template).every( + (key) => filter[key] === template[key] + ); + }; + + switch (boolType) { + case "must": + return this.listMust().filter(matchesTemplate); + case "should": + return this.listShould().filter(matchesTemplate); + case "must_not": + return this.listMustNot().filter(matchesTemplate); + default: + return []; } + } - clearMustNot() { - this.mustNot = []; + // Parsing and Serialization + merge(source) { + this.filtered = source.filtered; + if (source.size) { + this.size = source.size; } - - addShould(filter) { - if (!this.listShould().some(existingFilter => { - return Object.keys(filter).every(key => existingFilter[key] === filter[key]); - })) { - this.should.push(filter); - } + if (source.from) { + this.from = source.from; } - - listShould() { - return this.should; + if (source.fields && source.fields.length > 0) { + source.fields.forEach((field) => this.addField(field)); } - - removeShould(template) { - let removedCount = 0; - this.should = this.should.filter(filter => { - const matches = Object.keys(template).every(key => filter[key] === template[key]); - if (matches) { - removedCount++; - } - return !matches; - }); - return removedCount; + source.aggs.forEach((agg) => this.addAggregation(agg, true)); + source.must.forEach((filter) => this.addMust(filter)); + source.mustNot.forEach((filter) => this.addMustNot(filter)); + source.should.forEach((filter) => this.addShould(filter)); + if (source.minimumShouldMatch !== false) { + this.minimumShouldMatch = source.minimumShouldMatch; } + if (source.getQueryString()) { + this.setQueryString(source.getQueryString()); + } + if (source.sort && source.sort.length > 0) { + source.sort.reverse().forEach((sort) => this.prependSortBy(sort)); + } + if (source.source) { + this.addSourceFilters({ + include: source.getSourceIncludes(), + exclude: source.getSourceExcludes(), + }); + } + } + + objectify(params) { + params = params || {}; + const { + include_query_string = true, + include_filters = true, + include_paging = true, + include_sort = true, + include_fields = true, + include_aggregations = true, + include_source_filters = true, + } = params; + + const query_part = {}; + const bool = {}; + + if (this.queryString && include_query_string) { + Object.assign(query_part, this.queryString.objectify()); + } + + if (include_filters) { + if (this.must.length > 0) { + bool.must = this.must.map((filter) => filter.objectify()); + } + if (this.mustNot.length > 0) { + bool.must_not = this.mustNot.map((filter) => filter.objectify()); + } + if (this.should.length > 0) { + bool.should = this.should.map((filter) => filter.objectify()); + } + if (this.minimumShouldMatch !== false) { + bool.minimum_should_match = this.minimumShouldMatch; + } + } + + if ( + Object.keys(query_part).length === 0 && + Object.keys(bool).length === 0 + ) { + query_part.match_all = {}; + } else if ( + Object.keys(query_part).length === 0 && + Object.keys(bool).length > 0 + ) { + query_part.bool = bool; + } + + const obj = { + query: query_part, + }; + + if (include_paging) { + obj.from = this.getFrom(); + obj.size = this.getSize(); + } + + if (include_sort && this.sort.length > 0) { + obj.sort = this.sort.map((sort) => sort.objectify()); + } + + if (include_fields && this.fields.length > 0) { + obj.fields = this.fields.slice(); // Shallow copy of fields array + } + + if (include_aggregations && this.aggs.length > 0) { + obj.aggs = this.aggs.map((agg) => agg.objectify()); + } + + if (include_source_filters && this.source) { + obj._source = {}; + if (this.source.include && this.source.include.length > 0) { + obj._source.includes = this.source.include.slice(); // Shallow copy of include array + } + if (this.source.exclude && this.source.exclude.length > 0) { + obj._source.excludes = this.source.exclude.slice(); // Shallow copy of exclude array + } + } + + return obj; + } + + clone() { + const cloneParams = { + size: this.size, + from: this.from, + fields: [...this.fields], // Shallow copy of fields array + aggs: this.aggs.map((agg) => ({ ...agg })), // Shallow copy of aggs array + must: this.must.map((filter) => ({ ...filter })), // Shallow copy of must array + mustNot: this.mustNot.map((filter) => ({ ...filter })), // Shallow copy of mustNot array + should: this.should.map((filter) => ({ ...filter })), // Shallow copy of should array + minimumShouldMatch: this.minimumShouldMatch, + queryString: this.queryString ? { ...this.queryString } : null, // Shallow copy of queryString if present + sort: this.sort.map((sort) => ({ ...sort })), // Shallow copy of sort array + source: this.source + ? { + include: [...this.source.include], // Shallow copy of include array + exclude: [...this.source.exclude], // Shallow copy of exclude array + } + : null, + partialFields: this.partialFields, + scriptFields: this.scriptFields, + // Add any other properties that need to be cloned + }; + + return new es.Query(cloneParams); + } +}; - clearShould() { - this.should = []; - } +es.QueryString = class { + constructor(params) { + this.queryString = params.queryString || false; + this.defaultField = params.defaultField || false; + this.defaultOperator = params.defaultOperator || "OR"; + + this.fuzzify = params.fuzzify || false; // * or ~ + this.escapeSet = params.escapeSet || es.specialCharsSubSet; + this.pairs = params.pairs || es.characterPairs; + this.unEscapeSet = params.unEscapeSet || es.specialChars; + + if (params.raw) { + this.parse(params.raw); + } + } + + objectify() { + const qs = this._escape(this._fuzzify(this.queryString)); + const obj = { q: qs }; + if (this.defaultOperator) { + obj["q.op"] = this.defaultOperator; + } + if (this.defaultField) { + obj["df"] = this.defaultField; + } + return obj; + } + + clone() { + return new es.QueryString({ + queryString: this.queryString, + defaultField: this.defaultField, + defaultOperator: this.defaultOperator, + fuzzify: this.fuzzify, + escapeSet: this.escapeSet.slice(), // Shallow copy of escapeSet array + pairs: this.pairs.slice(), // Shallow copy of pairs array + unEscapeSet: this.unEscapeSet.slice(), // Shallow copy of unEscapeSet array + }); + } - // Interrogative Methods - hasFilters() { - return this.must.length > 0 || this.should.length > 0 || this.mustNot.length > 0; + parse(obj) { + if (obj.q) { + this.queryString = this._unescape(obj.q); } - - listFilters(params) { - const { boolType, template } = params; - const matchesTemplate = filter => { - return Object.keys(template).every(key => filter[key] === template[key]); - }; - - switch (boolType) { - case 'must': - return this.listMust().filter(matchesTemplate); - case 'should': - return this.listShould().filter(matchesTemplate); - case 'must_not': - return this.listMustNot().filter(matchesTemplate); - default: - return []; - } - } - - // Parsing and Serialization - merge(source) { - this.filtered = source.filtered; - if (source.size) { - this.size = source.size; - } - if (source.from) { - this.from = source.from; - } - if (source.fields && source.fields.length > 0) { - source.fields.forEach(field => this.addField(field)); - } - source.aggs.forEach(agg => this.addAggregation(agg, true)); - source.must.forEach(filter => this.addMust(filter)); - source.mustNot.forEach(filter => this.addMustNot(filter)); - source.should.forEach(filter => this.addShould(filter)); - if (source.minimumShouldMatch !== false) { - this.minimumShouldMatch = source.minimumShouldMatch; - } - if (source.getQueryString()) { - this.setQueryString(source.getQueryString()); - } - if (source.sort && source.sort.length > 0) { - source.sort.reverse().forEach(sort => this.prependSortBy(sort)); - } - if (source.source) { - this.addSourceFilters({ include: source.getSourceIncludes(), exclude: source.getSourceExcludes() }); - } + if (obj["q.op"]) { + this.defaultOperator = obj["q.op"]; } - - objectify(params) { - params = params || {}; - const { - include_query_string = true, - include_filters = true, - include_paging = true, - include_sort = true, - include_fields = true, - include_aggregations = true, - include_source_filters = true - } = params; - - const query_part = {}; - const bool = {}; - - if (this.queryString && include_query_string) { - Object.assign(query_part, this.queryString.objectify()); - } - - if (include_filters) { - if (this.must.length > 0) { - bool.must = this.must.map(filter => filter.objectify()); - } - if (this.mustNot.length > 0) { - bool.must_not = this.mustNot.map(filter => filter.objectify()); - } - if (this.should.length > 0) { - bool.should = this.should.map(filter => filter.objectify()); - } - if (this.minimumShouldMatch !== false) { - bool.minimum_should_match = this.minimumShouldMatch; - } - } - - if (Object.keys(query_part).length === 0 && Object.keys(bool).length === 0) { - query_part.match_all = {}; - } else if (Object.keys(query_part).length === 0 && Object.keys(bool).length > 0) { - query_part.bool = bool; - } - - const obj = { - query: query_part - }; - - if (include_paging) { - obj.from = this.getFrom(); - obj.size = this.getSize(); - } - - if (include_sort && this.sort.length > 0) { - obj.sort = this.sort.map(sort => sort.objectify()); - } - - if (include_fields && this.fields.length > 0) { - obj.fields = this.fields.slice(); // Shallow copy of fields array - } - - if (include_aggregations && this.aggs.length > 0) { - obj.aggs = this.aggs.map(agg => agg.objectify()); - } - - if (include_source_filters && this.source) { - obj._source = {}; - if (this.source.include && this.source.include.length > 0) { - obj._source.includes = this.source.include.slice(); // Shallow copy of include array - } - if (this.source.exclude && this.source.exclude.length > 0) { - obj._source.excludes = this.source.exclude.slice(); // Shallow copy of exclude array - } - } - - return obj; - } - - clone() { - const cloneParams = { - size: this.size, - from: this.from, - fields: [...this.fields], // Shallow copy of fields array - aggs: this.aggs.map(agg => ({ ...agg })), // Shallow copy of aggs array - must: this.must.map(filter => ({ ...filter })), // Shallow copy of must array - mustNot: this.mustNot.map(filter => ({ ...filter })), // Shallow copy of mustNot array - should: this.should.map(filter => ({ ...filter })), // Shallow copy of should array - minimumShouldMatch: this.minimumShouldMatch, - queryString: this.queryString ? { ...this.queryString } : null, // Shallow copy of queryString if present - sort: this.sort.map(sort => ({ ...sort })), // Shallow copy of sort array - source: this.source ? { - include: [...this.source.include], // Shallow copy of include array - exclude: [...this.source.exclude] // Shallow copy of exclude array - } : null, - partialFields: this.partialFields, - scriptFields: this.scriptFields - // Add any other properties that need to be cloned - }; - - return new es.Query(cloneParams); + if (obj.df) { + this.defaultField = obj.df; } -} + } -es.QueryString = class { - constructor(params) { - this.queryString = params.queryString || false; - this.defaultField = params.defaultField || false; - this.defaultOperator = params.defaultOperator || "OR"; - - this.fuzzify = params.fuzzify || false; // * or ~ - this.escapeSet = params.escapeSet || es.specialCharsSubSet; - this.pairs = params.pairs || es.characterPairs; - this.unEscapeSet = params.unEscapeSet || es.specialChars; - - if (params.raw) { - this.parse(params.raw); - } + _fuzzify(str) { + if (!this.fuzzify || !(this.fuzzify === "*" || this.fuzzify === "~")) { + return str; } - objectify() { - const qs = this._escape(this._fuzzify(this.queryString)); - const obj = { q: qs }; - if (this.defaultOperator) { - obj["q.op"] = this.defaultOperator; - } - if (this.defaultField) { - obj["df"] = this.defaultField; - } - return obj; - } - - clone() { - return new es.QueryString({ - queryString: this.queryString, - defaultField: this.defaultField, - defaultOperator: this.defaultOperator, - fuzzify: this.fuzzify, - escapeSet: this.escapeSet.slice(), // Shallow copy of escapeSet array - pairs: this.pairs.slice(), // Shallow copy of pairs array - unEscapeSet: this.unEscapeSet.slice() // Shallow copy of unEscapeSet array - }); + if (!(str.includes("*") || str.includes("~") || str.includes(":"))) { + return str; } - parse(obj) { - if (obj.q) { - this.queryString = this._unescape(obj.q); - } - if (obj["q.op"]) { - this.defaultOperator = obj["q.op"]; - } - if (obj.df) { - this.defaultField = obj.df; + let pq = ""; + const optparts = str.split(" "); + for (let i = 0; i < optparts.length; i++) { + let oip = optparts[i]; + if (oip.length > 0) { + oip += this.fuzzify; + if (this.fuzzify === "*") { + oip = "*" + oip; } + pq += oip + " "; + } } + return pq.trim(); + } - _fuzzify(str) { - if (!this.fuzzify || !(this.fuzzify === "*" || this.fuzzify === "~")) { - return str; - } - - if (!(str.includes('*') || str.includes('~') || str.includes(':'))) { - return str; - } + _escapeRegExp(string) { + return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); + } - let pq = ""; - const optparts = str.split(' '); - for (let i = 0; i < optparts.length; i++) { - let oip = optparts[i]; - if (oip.length > 0) { - oip += this.fuzzify; - if (this.fuzzify === "*") { - oip = "*" + oip; - } - pq += oip + " "; - } - } - return pq.trim(); - } + _replaceAll(string, find, replace) { + return string.replace(new RegExp(this._escapeRegExp(find), "g"), replace); + } - _escapeRegExp(string) { - return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); - } + _unReplaceAll(string, find) { + return string.replace( + new RegExp("\\\\" + this._escapeRegExp(find), "g"), + find + ); + } - _replaceAll(string, find, replace) { - return string.replace(new RegExp(this._escapeRegExp(find), 'g'), replace); - } + _paired(string, pair) { + const matches = + string.match(new RegExp(this._escapeRegExp(pair), "g")) || []; + return matches.length % 2 === 0; + } - _unReplaceAll(string, find) { - return string.replace(new RegExp("\\\\" + this._escapeRegExp(find), 'g'), find); + _escape(str) { + let scs = this.escapeSet.slice(); // Make a copy of escapeSet + for (let i = 0; i < this.pairs.length; i++) { + const char = this.pairs[i]; + if (!this._paired(str, char)) { + scs.push(char); + } } - - _paired(string, pair) { - const matches = (string.match(new RegExp(this._escapeRegExp(pair), "g"))) || []; - return matches.length % 2 === 0; + for (let i = 0; i < scs.length; i++) { + const char = scs[i]; + str = this._replaceAll(str, char, "\\" + char); } + return str; + } - _escape(str) { - let scs = this.escapeSet.slice(); // Make a copy of escapeSet - for (let i = 0; i < this.pairs.length; i++) { - const char = this.pairs[i]; - if (!this._paired(str, char)) { - scs.push(char); - } - } - for (let i = 0; i < scs.length; i++) { - const char = scs[i]; - str = this._replaceAll(str, char, "\\" + char); - } - return str; - } - - _unescape(str) { - for (let i = 0; i < this.unEscapeSet.length; i++) { - const char = this.unEscapeSet[i]; - str = this._unReplaceAll(str, char); - } - return str; + _unescape(str) { + for (let i = 0; i < this.unEscapeSet.length; i++) { + const char = this.unEscapeSet[i]; + str = this._unReplaceAll(str, char); } + return str; + } }; // Factories -es.aggregationFactory = function(type, params) { - for (const [key, value] of Object.entries(es)) { - if (es._classExtends(es[key], es.Aggregation)) { - if (es[key].type === type) { - // Convert Elasticsearch specific parameters to es if needed - if (type === "terms") { - params.field = params.field || false; - params.size = params.size || 10; // Use 'rows' for Solr, mapped from 'size' - params.orderBy = params.orderBy || "_count"; - if (params.orderBy[0] !== "_") { - params.orderBy = "_" + params.orderBy; - } - params.orderDir = params.orderDir || "desc"; - } - return new es[key](params); - } - } - } - throw new Error(`Unknown aggregation type: ${type}`); +es.aggregationFactory = function (type, params) { + for (const [key, value] of Object.entries(es)) { + if (es._classExtends(es[key], es.Aggregation)) { + if (es[key].type === type) { + // Convert Elasticsearch specific parameters to es if needed + if (type === "terms") { + params.field = params.field || false; + params.size = params.size || 10; // Use 'rows' for Solr, mapped from 'size' + params.orderBy = params.orderBy || "_count"; + if (params.orderBy[0] !== "_") { + params.orderBy = "_" + params.orderBy; + } + params.orderDir = params.orderDir || "desc"; + } + return new es[key](params); + } + } + } + throw new Error(`Unknown aggregation type: ${type}`); }; -es.filterFactory = function(type, params) { - // query string is a special case - if (type === "query_string") { - return new es.QueryString(params); - } - - // otherwise auto-detect - for (const [key, value] of Object.entries(es)) { - if (es._classExtends(es[key], es.Filter)) { - if (es[key].type === type) { - // Convert Elasticsearch specific parameters to Solr if needed - if (type === "terms") { - params.field = params.field || false; - params.values = params.values || []; - params.execution = params.execution || false; - } - return new es[key](params); - } - } - } - throw new Error(`Unknown filter type: ${type}`); +es.filterFactory = function (type, params) { + // query string is a special case + if (type === "query_string") { + return new es.QueryString(params); + } + + // otherwise auto-detect + for (const [key, value] of Object.entries(es)) { + if (es._classExtends(es[key], es.Filter)) { + if (es[key].type === type) { + // Convert Elasticsearch specific parameters to Solr if needed + if (type === "terms") { + params.field = params.field || false; + params.values = params.values || []; + params.execution = params.execution || false; + } + return new es[key](params); + } + } + } + throw new Error(`Unknown filter type: ${type}`); }; // Filter extended classes starts here es.TermFilter = class extends es.Filter { - static type = "term"; + static type = "term"; - constructor(params) { - super(params); - // this.filter handled by superclass - this.value = params.value || false; + constructor(params) { + super(params); + // this.filter handled by superclass + this.value = params.value || false; - if (params.raw) { - this.parse(params.raw); - } + if (params.raw) { + this.parse(params.raw); } + } - matches(other) { - // ask the parent object first - let pm = this._baseMatch(other); - if (!pm) { - return false; - } - // value (if set) must match - if (other.value && other.value !== this.value) { - return false; - } - - return true; + matches(other) { + // ask the parent object first + let pm = this._baseMatch(other); + if (!pm) { + return false; } - - objectify() { - // Solr-specific object structure - var obj = {}; - obj[this.field] = this.value; - return obj; + // value (if set) must match + if (other.value && other.value !== this.value) { + return false; } - parse(obj) { - // Solr-specific parsing - this.field = Object.keys(obj)[0]; - this.value = obj[this.field]; - } -} + return true; + } + + objectify() { + // Solr-specific object structure + var obj = {}; + obj[this.field] = this.value; + return obj; + } + + parse(obj) { + // Solr-specific parsing + this.field = Object.keys(obj)[0]; + this.value = obj[this.field]; + } +}; es.TermsFilter = class extends es.Filter { - static type = "terms"; + static type = "terms"; - constructor(params) { - super(params); - // this.field handled by superclass - this.values = params.values || false; - this.execution = params.execution || false; + constructor(params) { + super(params); + // this.field handled by superclass + this.values = params.values || false; + this.execution = params.execution || false; - if (params.raw) { - this.parse(params.raw); - } + if (params.raw) { + this.parse(params.raw); } + } - matches(other) { - // ask the parent object first - let pm = this._baseMatch(other); - if (!pm) { - return false; - } + matches(other) { + // ask the parent object first + let pm = this._baseMatch(other); + if (!pm) { + return false; + } - // values (if set) must be the same list - if (other.values) { - if (other.values.length !== this.values.length) { - return false; - } - for (var i = 0; i < other.values.length; i++) { - if ($.inArray(other.values[i], this.values) === -1) { - return false; - } - } + // values (if set) must be the same list + if (other.values) { + if (other.values.length !== this.values.length) { + return false; + } + for (var i = 0; i < other.values.length; i++) { + if ($.inArray(other.values[i], this.values) === -1) { + return false; } - - return true; + } } - objectify() { - var val = this.values || []; - var filterQuery = val.map(value => `${this.field}:${value}`).join(' OR '); - return { fq: filterQuery }; - } + return true; + } - parse(obj) { - if (obj.fq) { - let terms = obj.fq.split(' OR '); - let field = terms[0].split(':')[0]; - let values = terms.map(term => term.split(':')[1]); + objectify() { + var val = this.values || []; + var filterQuery = val.map((value) => `${this.field}:${value}`).join(" OR "); + return { fq: filterQuery }; + } - this.field = field; - this.values = values; - } - } + parse(obj) { + if (obj.fq) { + let terms = obj.fq.split(" OR "); + let field = terms[0].split(":")[0]; + let values = terms.map((term) => term.split(":")[1]); - add_term(term) { - if (!this.values) { - this.values = []; - } - if ($.inArray(term, this.values) === -1) { - this.values.push(term); - } + this.field = field; + this.values = values; } + } - has_term(term) { - if (!this.values) { - return false; - } - return $.inArray(term, this.values) >= 0; + add_term(term) { + if (!this.values) { + this.values = []; } - - remove_term(term) { - if (!this.values) { - return; - } - var idx = $.inArray(term, this.values); - if (idx >= 0) { - this.values.splice(idx, 1); - } + if ($.inArray(term, this.values) === -1) { + this.values.push(term); } + } - has_terms() { - return (this.values !== false && this.values.length > 0); + has_term(term) { + if (!this.values) { + return false; } + return $.inArray(term, this.values) >= 0; + } - term_count() { - return this.values === false ? 0 : this.values.length; + remove_term(term) { + if (!this.values) { + return; } - - clear_terms() { - this.values = false; + var idx = $.inArray(term, this.values); + if (idx >= 0) { + this.values.splice(idx, 1); } -}; + } + has_terms() { + return this.values !== false && this.values.length > 0; + } -// Aggregation extended classes starts here -es.TermsAggregation = class extends es.Aggregation { - static type = "terms"; + term_count() { + return this.values === false ? 0 : this.values.length; + } - constructor(params) { - super(params); - this.field = params.field || false; - this.size = params.size || 10; // 'size' in Elasticsearch, will convert to 'rows' for Solr + clear_terms() { + this.values = false; + } +}; - // set the ordering for the first time +// Aggregation extended classes starts here +es.TermsAggregation = class extends es.Aggregation { + static type = "terms"; + + constructor(params) { + super(params); + this.field = params.field || false; + this.size = params.size || 10; // 'size' in Elasticsearch, will convert to 'rows' for Solr + + // set the ordering for the first time + this.orderBy = "_count"; + if (params.orderBy) { + this.orderBy = params.orderBy; + if (this.orderBy[0] !== "_") { + this.orderBy = "_" + this.orderBy; + } + } + this.orderDir = params.orderDir || "desc"; + + if (params.raw) { + this.parse(params.raw); + } + } + + // provide a method to set and normalize the ordering in future + setOrdering(orderBy, orderDir) { + this.orderBy = orderBy; + if (this.orderBy[0] !== "_") { + this.orderBy = "_" + this.orderBy; + } + this.orderDir = orderDir; + } + + objectify() { + // Solr facets configuration + const body = { + field: this.field, + rows: this.size, // Convert 'size' to 'rows' for Solr + order: {}, + }; + + // Translate Elasticsearch orderBy to Solr sort + let solrSort = ""; + if (this.orderBy === "_count") { + solrSort = "count"; + } else if (this.orderBy === "_term") { + solrSort = "index"; + } + body.order[solrSort] = this.orderDir; + + return this._make_aggregation(es.TermsAggregation.type, body); + } + + parse(obj) { + const body = this._parse_wrapper(obj, es.TermsAggregation.type); + this.field = body.field; + if (body.rows) { + this.size = body.rows; // Convert 'rows' to 'size' + } + if (body.order) { + const solrSort = Object.keys(body.order)[0]; + this.orderDir = body.order[solrSort]; + + // Translate Solr sort back to Elasticsearch orderBy + if (solrSort === "count") { this.orderBy = "_count"; - if (params.orderBy) { - this.orderBy = params.orderBy; - if (this.orderBy[0] !== "_") { - this.orderBy = "_" + this.orderBy; - } - } - this.orderDir = params.orderDir || "desc"; - - if (params.raw) { - this.parse(params.raw); - } - } - - // provide a method to set and normalize the ordering in future - setOrdering(orderBy, orderDir) { - this.orderBy = orderBy; - if (this.orderBy[0] !== "_") { - this.orderBy = "_" + this.orderBy; - } - this.orderDir = orderDir; - } - - objectify() { - // Solr facets configuration - const body = { - field: this.field, - rows: this.size, // Convert 'size' to 'rows' for Solr - order: {} - }; - - // Translate Elasticsearch orderBy to Solr sort - let solrSort = ""; - if (this.orderBy === "_count") { - solrSort = "count"; - } else if (this.orderBy === "_term") { - solrSort = "index"; - } - body.order[solrSort] = this.orderDir; - - return this._make_aggregation(es.TermsAggregation.type, body); - } - - parse(obj) { - const body = this._parse_wrapper(obj, es.TermsAggregation.type); - this.field = body.field; - if (body.rows) { - this.size = body.rows; // Convert 'rows' to 'size' - } - if (body.order) { - const solrSort = Object.keys(body.order)[0]; - this.orderDir = body.order[solrSort]; - - // Translate Solr sort back to Elasticsearch orderBy - if (solrSort === "count") { - this.orderBy = "_count"; - } else if (solrSort === "index") { - this.orderBy = "_term"; - } - } + } else if (solrSort === "index") { + this.orderBy = "_term"; + } } + } }; - - es.doQuery = (params) => { - const { success, error, complete, search_url, query, datatype } = params; - - const solrArgs = this._es2solr({ query : query }); - const searchUrl = search_url; - // Generate the Solr query URL - const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); - var error_callback = es.queryError(error); - var success_callback = es.querySuccess(success, error_callback); - - // Execution of solr query - $.get({ - url: fullUrl, - datatype: datatype ? datatype : "jsonp", - success: success_callback, - error: error_callback, - jsonp: 'json.wrf' - }); - + const { success, error, complete, search_url, query, datatype } = params; + + const solrArgs = this._es2solr({ query: query }); + const searchUrl = search_url; + // Generate the Solr query URL + const fullUrl = this._args2URL({ baseUrl: searchUrl, args: solrArgs }); + var error_callback = es.queryError(error); + var success_callback = es.querySuccess(success, error_callback); + + // Execution of solr query + $.get({ + url: fullUrl, + datatype: datatype ? datatype : "jsonp", + success: success_callback, + error: error_callback, + jsonp: "json.wrf", + }); }; es.querySuccess = function (callback, error_callback) { - return function(data) { - if (data.hasOwnProperty("error")) { - error_callback(data); - return; - } - - var result = new es.Result({raw: data}); - callback(result); - } + return function (data) { + if (data.hasOwnProperty("error")) { + error_callback(data); + return; + } + + var result = new es.Result({ raw: data }); + callback(result); + }; }; es.queryError = function (callback) { - return function(data) { - if (callback) { - callback(data); - } else { - throw new Error(data); - } - } + return function (data) { + if (callback) { + callback(data); + } else { + throw new Error(data); + } + }; }; es.Result = class { - constructor(params) { - this.data = JSON.parse(params.raw); - } - - buckets(facet_name) { - if (this.data.facet_counts) { - if (this.data.facet_counts.facet_fields && this.data.facet_counts.facet_fields[facet_name]) { - return this._convertFacetToBuckets(this.data.facet_counts.facet_fields[facet_name]); - } else if (this.data.facet_counts.facet_queries && this.data.facet_counts.facet_queries[facet_name]) { - return this._convertFacetToBuckets(this.data.facet_counts.facet_queries[facet_name]); - } - } - return []; - } - - _convertFacetToBuckets(facet) { - let buckets = []; - for (let i = 0; i < facet.length; i += 2) { - buckets.push({ - key: facet[i], - doc_count: facet[i + 1] - }); - } - return buckets; - } - - aggregation(facet_name) { - return { - buckets: this.buckets(facet_name) - }; - } - - results() { - var res = []; - if (this.data.response && this.data.response.docs) { - for (var i = 0; i < this.data.response.docs.length; i++) { - res.push(this.data.response.docs[i]); - } - } - return res; - } - - total() { - if (this.data.response && this.data.response.numFound !== undefined) { - return parseInt(this.data.response.numFound); - } - return false; - } + constructor(params) { + this.data = JSON.parse(params.raw); + } + + buckets(facet_name) { + if (this.data.facet_counts) { + if ( + this.data.facet_counts.facet_fields && + this.data.facet_counts.facet_fields[facet_name] + ) { + return this._convertFacetToBuckets( + this.data.facet_counts.facet_fields[facet_name] + ); + } else if ( + this.data.facet_counts.facet_queries && + this.data.facet_counts.facet_queries[facet_name] + ) { + return this._convertFacetToBuckets( + this.data.facet_counts.facet_queries[facet_name] + ); + } + } + return []; + } + + _convertFacetToBuckets(facet) { + let buckets = []; + for (let i = 0; i < facet.length; i += 2) { + buckets.push({ + key: facet[i], + doc_count: facet[i + 1], + }); + } + return buckets; + } + + aggregation(facet_name) { + return { + buckets: this.buckets(facet_name), + }; + } + + results() { + var res = []; + if (this.data.response && this.data.response.docs) { + for (var i = 0; i < this.data.response.docs.length; i++) { + res.push(this.data.response.docs[i]); + } + } + return res; + } + + total() { + if (this.data.response && this.data.response.numFound !== undefined) { + return parseInt(this.data.response.numFound); + } + return false; + } }; - // Helper functions // Method to convert es query to Solr query function _es2solr({ query }) { - const solrQuery = {}; - let solrFacets = [] - - // Handle the query part - if (Object.entries(query.query).length > 0) { - const queryPart = query.query; - if (queryPart.match) { - const field = Object.keys(queryPart.match)[0]; - const value = queryPart.match[field]; - solrQuery.q = `${field}:${value}`; - } else if (queryPart.range) { - const fields = Object.keys(queryPart.range); - const rangeQueries = fields.map(field => { - const range = queryPart.range[field]; - return `${field}:[${range.gte || '*'} TO ${range.lte || '*'}]`; - }); - - // Join the range queries with OR if there are multiple - solrQuery.fq = rangeQueries.length > 1 ? `(${rangeQueries.join(' OR ')})` : rangeQueries[0]; - } else if (queryPart.match_all) { - solrQuery.q = `*:*`; - } - } else { - solrQuery.q = `*:*`; - } - - // Handle pagination - if (query.from !== undefined) { - if (typeof query.from == "boolean" && !query.from) { - solrQuery.start = 0 - } else { - solrQuery.start = query.from; - } - } - if (query.size !== undefined) { - if (typeof query.size == "boolean" && !query.size) { - solrQuery.rows = 10 - } else { - solrQuery.rows = query.size; - } - - } - - // Handle sorting - if (query && query.sort && query.sort.length > 0) { - solrQuery.sort = query.sort.map(sortOption => { - const sortField = sortOption.field; - const sortOrder = sortOption.order === "desc" ? "desc" : "asc"; - return `${sortField} ${sortOrder}`; - }).join(', '); - } - - if (query.queryStrings && query.queryStrings.length > 0) { - query.queryStrings.forEach((query, queryIndex) => { - const esQueryString = query.queryString; - const fields = query.fields; - - if (typeof esQueryString == 'boolean') { - throw new Error('Search string needs to be a string, got boolean'); + const solrQuery = {}; + let solrFacets = []; + + // Handle the query part + if (Object.entries(query.query).length > 0) { + const queryPart = query.query; + if (queryPart.match) { + const field = Object.keys(queryPart.match)[0]; + const value = queryPart.match[field]; + solrQuery.q = `${field}:${value}`; + } else if (queryPart.range) { + const fields = Object.keys(queryPart.range); + const rangeQueries = fields.map((field) => { + const range = queryPart.range[field]; + return `${field}:[${range.gte || "*"} TO ${range.lte || "*"}]`; + }); + + // Join the range queries with OR if there are multiple + solrQuery.q = + rangeQueries.length > 1 + ? `(${rangeQueries.join(" OR ")})` + : rangeQueries[0]; + } else if (queryPart.match_all) { + solrQuery.q = `*:*`; + } + } else { + solrQuery.q = `*:*`; + } + + // Handle pagination + if (query.from !== undefined) { + if (typeof query.from == "boolean" && !query.from) { + solrQuery.start = 0; + } else { + solrQuery.start = query.from; + } + } + if (query.size !== undefined) { + if (typeof query.size == "boolean" && !query.size) { + solrQuery.rows = 10; + } else { + solrQuery.rows = query.size; + } + } + + // Handle sorting + if (query && query.sort && query.sort.length > 0) { + solrQuery.sort = query.sort + .map((sortOption) => { + const sortField = sortOption.field; + const sortOrder = sortOption.order === "desc" ? "desc" : "asc"; + return `${sortField} ${sortOrder}`; + }) + .join(", "); + } + + if (query.queryStrings && query.queryStrings.length > 0) { + query.queryStrings.forEach((query, queryIndex) => { + const esQueryString = query.queryString; + const fields = query.fields; + + if (typeof esQueryString == "boolean") { + throw new Error("Search string needs to be a string, got boolean"); + } + + if (esQueryString !== "" && Array.isArray(fields) && fields.length > 0) { + let queryPart = ""; // To hold the query part for this set of fields + + fields.forEach((fieldConfig, index) => { + const { field, operator = "OR" } = fieldConfig; + + // Cleaning solr query only in case of *:* + if (solrQuery.q == "*:*") { + solrQuery.q = ""; + } + + if (queryPart) { + queryPart += ` ${operator} ${field}:${esQueryString}`; + } else { + queryPart = `${field}:${esQueryString}`; + } + }); + + // If there are multiple fields, wrap the query in parentheses + if (fields.length > 1) { + queryPart = `(${queryPart})`; } - if (esQueryString !== "" && Array.isArray(fields) && fields.length > 0) { - let queryPart = ""; // To hold the query part for this set of fields - - fields.forEach((fieldConfig, index) => { - const { field, operator = "OR" } = fieldConfig; - - // Cleaning solr query only in case of *:* - if (solrQuery.q == "*:*") { - solrQuery.q = ""; - } - - if (queryPart) { - queryPart += ` ${operator} ${field}:${esQueryString}`; - } else { - queryPart = `${field}:${esQueryString}`; - } - }); - - // If there are multiple fields, wrap the query in parentheses - if (fields.length > 1) { - queryPart = `(${queryPart})`; - } - - // Append to the existing solrQuery.q - if (solrQuery.q) { - solrQuery.q += ` AND ${queryPart}`; - } else { - solrQuery.q = queryPart; - } + // Append to the existing solrQuery.q + if (solrQuery.q) { + solrQuery.q += ` AND ${queryPart}`; + } else { + solrQuery.q = queryPart; } + } }); - } + } - if (query.queryString && query.queryString.queryString) { - const esQueryString = query.queryString.queryString; - const searchField = query.queryString.defaultField; - let operator = query.queryString.defaultOperator; + if (query.queryString && query.queryString.queryString) { + const esQueryString = query.queryString.queryString; + const searchField = query.queryString.defaultField; + let operator = query.queryString.defaultOperator; - if(typeof esQueryString == 'boolean') { - throw new Error('Search string needs to be string got boolean'); - } + if (typeof esQueryString == "boolean") { + throw new Error("Search string needs to be string got boolean"); + } - if (operator == '') { - operator = "OR" - } + if (operator == "") { + operator = "OR"; + } - if (esQueryString != "") { - if (typeof searchField == 'boolean') { - solrQuery.q = `${solrQuery.q} ${operator} ${esQueryString}`; - } else { - solrQuery.q = `${solrQuery.q} ${operator} ${searchField}:${esQueryString}`; - } - } + if (esQueryString != "") { + if (typeof searchField == "boolean") { + solrQuery.q = `${solrQuery.q} ${operator} ${esQueryString}`; + } else { + solrQuery.q = `${solrQuery.q} ${operator} ${searchField}:${esQueryString}`; + } } + } - if (query && query.aggs && query.aggs.length > 0) { - solrQuery.facets = query.aggs.map(agg => agg.field); - // query.aggs.forEach(agg => { - // _covertAffLimitAndSortToFacet(agg , solrQuery); - // }); + if (query && query.aggs && query.aggs.length > 0) { + solrQuery.facets = query.aggs.map((agg) => agg.field); + // query.aggs.forEach(agg => { + // _covertAffLimitAndSortToFacet(agg , solrQuery); + // }); - solrQuery.facet = true - } + solrQuery.facet = true; + } - if(query && query.must && query.must.length > 0) { - query.must.forEach(mustQuery => { - const term = mustQuery.term; - const field = Object.keys(term)[0]; - const value = term[field]; + if (query && query.must && query.must.length > 0) { + query.must.forEach((mustQuery) => { + const term = mustQuery.term; + const field = Object.keys(term)[0]; + const value = term[field]; - if(solrQuery.q == "*:*") { - solrQuery.q = "" - } + if (solrQuery.q == "*:*") { + solrQuery.q = ""; + } - if(solrQuery.q) { - solrQuery.q += ` AND ${field}:${value}`; - } else { - solrQuery.q = `${field}:${value}`; - } - }); - } + if (solrQuery.q) { + solrQuery.q += ` AND ${field}:${value}`; + } else { + solrQuery.q = `${field}:${value}`; + } + }); + } - if(query && query.mustNot && query.mustNot.length > 0) { - query.mustNot.forEach(mustNotq => { - const term = mustQuery.term; - const field = Object.keys(term)[0]; - const value = term[field]; + if (query && query.mustNot && query.mustNot.length > 0) { + query.mustNot.forEach((mustNotq) => { + const term = mustQuery.term; + const field = Object.keys(term)[0]; + const value = term[field]; - solrQuery.q = `-${field}:${value}` - }); - } + solrQuery.q = `-${field}:${value}`; + }); + } - if(query && query.should && query.should.length > 0) { - query.should.forEach((shouldQ , index) => { - const term = mustQuery.term; - const field = Object.keys(term)[0]; - const value = term[field]; + if (query && query.should && query.should.length > 0) { + query.should.forEach((shouldQ, index) => { + const term = mustQuery.term; + const field = Object.keys(term)[0]; + const value = term[field]; - solrQuery.q = `${field}:${value}^1.0`; - }); - } + solrQuery.q = `${field}:${value}^1.0`; + }); + } - solrQuery.wt = "json" + solrQuery.wt = "json"; - return solrQuery; + return solrQuery; } -function _args2URL({ baseUrl, args }) { - const qParts = Object.keys(args).flatMap(k => { - const v = args[k]; - if (Array.isArray(v)) { - - if (k === 'facets') { - const result = v[0].split(',').map(item => `facet.field=${encodeURIComponent(item)}`).join('&'); - return result - } else { - return v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`); - } - } - return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; - }); - - const qs = qParts.join("&"); - return `${baseUrl}?${qs}`; - +function _args2URL({ baseUrl, args }) { + const qParts = Object.keys(args).flatMap((k) => { + const v = args[k]; + if (Array.isArray(v)) { + if (k === "facets") { + const result = v[0] + .split(",") + .map((item) => `facet.field=${encodeURIComponent(item)}`) + .join("&"); + return result; + } else { + return v.map( + (item) => `${encodeURIComponent(k)}=${encodeURIComponent(item)}` + ); + } + } + return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; + }); + + const qs = qParts.join("&"); + return `${baseUrl}?${qs}`; } -function _covertAffLimitAndSortToFacet(agg , solrQuery) { - const fields = agg.field.split(","); - const size = agg.size || 10; // default size if not specified - const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr - const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified - - fields.forEach(field => { - solrQuery[`f.${field}.facet.limit`] = size - solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}` - }); -} +function _covertAffLimitAndSortToFacet(agg, solrQuery) { + const fields = agg.field.split(","); + const size = agg.size || 10; // default size if not specified + const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr + const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified + fields.forEach((field) => { + solrQuery[`f.${field}.facet.limit`] = size; + solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}`; + }); +} -es.getParam = function(value, def) { - return value !== undefined ? value : def; -} \ No newline at end of file +es.getParam = function (value, def) { + return value !== undefined ? value : def; +}; From 30994ff0e9fe3d1b12d55daa342db7cb01ae336a Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Wed, 23 Oct 2024 12:17:19 +0530 Subject: [PATCH 15/21] Bug for query.must fixed for solr --- src/datasources/solr9x.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index e324175..dab0daa 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -1168,9 +1168,17 @@ function _es2solr({ query }) { if (query && query.must && query.must.length > 0) { query.must.forEach((mustQuery) => { - const term = mustQuery.term; - const field = Object.keys(term)[0]; - const value = term[field]; + let term, + field, + value = ""; + if (mustQuery.term) { + term = mustQuery.term; + field = Object.keys(term)[0]; + value = term[field]; + } else { + field = mustQuery.field; + value = mustQuery.value; + } if (solrQuery.q == "*:*") { solrQuery.q = ""; From 97fdf304e249f24a2bf924a5b0072275ed21f469 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Mon, 11 Nov 2024 15:09:32 +0530 Subject: [PATCH 16/21] Correction to the solr size limit code --- src/datasources/solr9x.js | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index dab0daa..ae9db19 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -957,7 +957,7 @@ es.querySuccess = function (callback, error_callback) { error_callback(data); return; } - + console.log("Got result as", data); var result = new es.Result({ raw: data }); callback(result); }; @@ -975,7 +975,11 @@ es.queryError = function (callback) { es.Result = class { constructor(params) { - this.data = JSON.parse(params.raw); + if (typeof params.raw == "string") { + this.data = JSON.parse(params.raw); + } else { + this.data = params.raw; + } } buckets(facet_name) { @@ -1158,10 +1162,12 @@ function _es2solr({ query }) { } if (query && query.aggs && query.aggs.length > 0) { - solrQuery.facets = query.aggs.map((agg) => agg.field); - // query.aggs.forEach(agg => { - // _covertAffLimitAndSortToFacet(agg , solrQuery); - // }); + solrQuery.facets = query.aggs + .filter((agg) => { + // Log the field value + return typeof agg.field === "string" && agg.field; // Filter condition + }) + .map((agg) => agg.field); solrQuery.facet = true; } @@ -1222,10 +1228,10 @@ function _args2URL({ baseUrl, args }) { const v = args[k]; if (Array.isArray(v)) { if (k === "facets") { - const result = v[0] - .split(",") + const result = v .map((item) => `facet.field=${encodeURIComponent(item)}`) .join("&"); + return result; } else { return v.map( @@ -1240,16 +1246,14 @@ function _args2URL({ baseUrl, args }) { return `${baseUrl}?${qs}`; } -function _covertAffLimitAndSortToFacet(agg, solrQuery) { - const fields = agg.field.split(","); +function _convertAffLimitAndSortToFacet(agg, solrQuery) { + const field = agg.field; const size = agg.size || 10; // default size if not specified - const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr - const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified + // const order = agg.orderBy === "_count" ? "count" : "index"; // mapping orderBy to Solr + // const direction = agg.orderDir === "desc" ? "desc" : "asc"; // default direction if not specified - fields.forEach((field) => { - solrQuery[`f.${field}.facet.limit`] = size; - solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}`; - }); + solrQuery[`f.${field}.facet.limit`] = size; + // solrQuery[`f.${field}.facet.sort`] = `${order}|${direction}`; } es.getParam = function (value, def) { From d94aba9018354f82c54914fc5034a2119f1626db Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Mon, 2 Dec 2024 13:27:21 +0530 Subject: [PATCH 17/21] queryStrings are missing when facet was selected, code handled --- src/components/RefiningANDTermSelector.js | 654 +++--- src/datasources/solr9x.js | 5 +- src/edges.js | 2236 +++++++++++---------- 3 files changed, 1501 insertions(+), 1394 deletions(-) diff --git a/src/components/RefiningANDTermSelector.js b/src/components/RefiningANDTermSelector.js index fbeb37a..d78ba3c 100644 --- a/src/components/RefiningANDTermSelector.js +++ b/src/components/RefiningANDTermSelector.js @@ -2,350 +2,378 @@ // requires: edges // requires: edges.util -if (!window.hasOwnProperty("edges")) { edges = {}} -if (!edges.hasOwnProperty("components")) { edges.components = {}} +if (!window.hasOwnProperty("edges")) { + edges = {}; +} +if (!edges.hasOwnProperty("components")) { + edges.components = {}; +} edges.components.RefiningANDTermSelector = class extends edges.Component { - constructor(params) { - super(params); + constructor(params) { + super(params); - this.field = edges.util.getParam(params, "field"); + this.field = edges.util.getParam(params, "field"); - // how many terms should the facet limit to - this.size = edges.util.getParam(params, "size", 10); + // how many terms should the facet limit to + this.size = edges.util.getParam(params, "size", 10); - // which ordering to use term/count and asc/desc - this.orderBy = edges.util.getParam(params, "orderBy", "count"); - this.orderDir = edges.util.getParam(params, "orderDir", "desc"); + // which ordering to use term/count and asc/desc + this.orderBy = edges.util.getParam(params, "orderBy", "count"); + this.orderDir = edges.util.getParam(params, "orderDir", "desc"); - // any values that should be removed from the normal ordering and displayed at the end insead - this.pushToBottom = edges.util.getParam(params, "pushToBottom", []); + // any values that should be removed from the normal ordering and displayed at the end insead + this.pushToBottom = edges.util.getParam(params, "pushToBottom", []); - // number of facet terms below which the facet is disabled - this.deactivateThreshold = edges.util.getParam(params, "deactivateThreshold", false); + // number of facet terms below which the facet is disabled + this.deactivateThreshold = edges.util.getParam( + params, + "deactivateThreshold", + false + ); - // should the terms facet ignore empty strings in display - this.ignoreEmptyString = edges.util.getParam(params, "ignoreEmptyString", true); + // should the terms facet ignore empty strings in display + this.ignoreEmptyString = edges.util.getParam( + params, + "ignoreEmptyString", + true + ); - // should filters defined in the baseQuery be excluded from the selector - this.excludePreDefinedFilters = edges.util.getParam(params, "excludePreDefinedFilters", true); + // should filters defined in the baseQuery be excluded from the selector + this.excludePreDefinedFilters = edges.util.getParam( + params, + "excludePreDefinedFilters", + true + ); - // provide a map of values for terms to displayable terms, or a function - // which can be used to translate terms to displyable values - this.valueMap = edges.util.getParam(params, "valueMap", false); - this.valueFunction = edges.util.getParam(params, "valueFunction", false); + // provide a map of values for terms to displayable terms, or a function + // which can be used to translate terms to displyable values + this.valueMap = edges.util.getParam(params, "valueMap", false); + this.valueFunction = edges.util.getParam(params, "valueFunction", false); - // due to a limitation in elasticsearch's clustered node facet counts, we need to inflate - // the number of facet results we need to ensure that the results we actually want are - // accurate. This option tells us by how much. - this.inflation = edges.util.getParam(params, "inflation", 100); + // due to a limitation in elasticsearch's clustered node facet counts, we need to inflate + // the number of facet results we need to ensure that the results we actually want are + // accurate. This option tells us by how much. + this.inflation = edges.util.getParam(params, "inflation", 100); - this.active = edges.util.getParam(params, "active", true); + this.active = edges.util.getParam(params, "active", true); - // whether this component updates itself on every request, or whether it is static - // throughout its lifecycle. One of "update" or "static" - this.lifecycle = edges.util.getParam(params, "lifecycle", "update"); + // whether this component updates itself on every request, or whether it is static + // throughout its lifecycle. One of "update" or "static" + this.lifecycle = edges.util.getParam(params, "lifecycle", "update"); - // for the static lifecycle, should the counts be synchronised with the data - this.syncCounts = edges.util.getParam(params, "syncCounts", true); + // for the static lifecycle, should the counts be synchronised with the data + this.syncCounts = edges.util.getParam(params, "syncCounts", true); - ////////////////////////////////////////// - // properties used to store internal state + ////////////////////////////////////////// + // properties used to store internal state - // filters that have been selected via this component - // {display: , term: } - this.filters = []; + // filters that have been selected via this component + // {display: , term: } + this.filters = []; - // values that the renderer should render - // wraps an object (so the list is ordered) which in turn is the - // { display: , term: , count: } - this.values = false; - } + // values that the renderer should render + // wraps an object (so the list is ordered) which in turn is the + // { display: , term: , count: } + this.values = false; + } - ////////////////////////////////////////// - // overrides on the parent object's standard functions + ////////////////////////////////////////// + // overrides on the parent object's standard functions - init(edge) { - // first kick the request up to the superclass - super.init(edge) + init(edge) { + // first kick the request up to the superclass + super.init(edge); - if (this.lifecycle === "static") { - this.listAll(); - } + if (this.lifecycle === "static") { + this.listAll(); } - - contrib(query) { - let params = { - name: this.id, - field: this.field, - orderBy: this.orderBy, - orderDir: this.orderDir - }; - if (this.size) { - params["size"] = this.size - } - query.addAggregation( - new es.TermsAggregation(params) - ); + } + + contrib(query) { + let params = { + name: this.id, + field: this.field, + orderBy: this.orderBy, + orderDir: this.orderDir, }; - - synchronise() { - // reset the state of the internal variables - if (this.lifecycle === "update") { - // if we are in the "update" lifecycle, then reset and read all the values - this.values = []; - if (this.edge.result) { - this._readValues({result: this.edge.result}); - } - } else if (this.lifecycle === "static" && this.syncCounts) { - if (this.edge.result) { - this._syncCounts({result: this.edge.result}) - } - } - this.filters = []; - - // extract all the filter values that pertain to this selector - let filters = this.edge.currentQuery.listMust(new es.TermFilter({field: this.field})); - for (let i = 0; i < filters.length; i++) { - let val = filters[i].value; - val = this._translate(val); - this.filters.push({display: val, term: filters[i].value}); - } - }; - - _syncCounts(params) { - let result = params.result; - let buckets = result.buckets(this.id); - - for (let j = 0; j < this.values.length; j++) { - let updated = false; - for (let i = 0; i < buckets.length; i++) { - let bucket = buckets[i]; - if (this.values[j].term === bucket.key) { - this.values[j].count = bucket.doc_count; - updated = true; - } - } - if (!updated) { - this.values[j].count = 0 - } + if (this.size) { + params["size"] = this.size; + } + query.addAggregation(new es.TermsAggregation(params)); + } + + synchronise() { + // reset the state of the internal variables + if (this.lifecycle === "update") { + // if we are in the "update" lifecycle, then reset and read all the values + this.values = []; + if (this.edge.result) { + this._readValues({ result: this.edge.result }); + } + } else if (this.lifecycle === "static" && this.syncCounts) { + if (this.edge.result) { + this._syncCounts({ result: this.edge.result }); + } + } + this.filters = []; + + // extract all the filter values that pertain to this selector + let filters = this.edge.currentQuery.listMust( + new es.TermFilter({ field: this.field }) + ); + for (let i = 0; i < filters.length; i++) { + let val = filters[i].value; + let translate_val = this._translate(val); + + if (val != translate_val) { + this.filters.push({ display: translate_val, term: val }); + } else { + this.filters.push({ display: val, term: val }); + } + } + } + + _syncCounts(params) { + let result = params.result; + let buckets = result.buckets(this.id); + + for (let j = 0; j < this.values.length; j++) { + let updated = false; + for (let i = 0; i < buckets.length; i++) { + let bucket = buckets[i]; + if (this.values[j].term === bucket.key) { + this.values[j].count = bucket.doc_count; + updated = true; } + } + if (!updated) { + this.values[j].count = 0; + } } + } - _readValues(params) { - let result = params.result; + _readValues(params) { + let result = params.result; - // assign the terms and counts from the aggregation - let buckets = result.buckets(this.id); + // assign the terms and counts from the aggregation + let buckets = result.buckets(this.id); - if (this.deactivateThreshold) { - if (buckets.length < this.deactivateThreshold) { - this.active = false - } else { - this.active = true; - } - } + if (this.deactivateThreshold) { + if (buckets.length < this.deactivateThreshold) { + this.active = false; + } else { + this.active = true; + } + } - // list all of the pre-defined filters for this field from the baseQuery - let predefined = []; - if (this.excludePreDefinedFilters && this.edge.baseQuery) { - predefined = this.edge.baseQuery.listMust(new es.TermFilter({field: this.field})); - } + // list all of the pre-defined filters for this field from the baseQuery + let predefined = []; + if (this.excludePreDefinedFilters && this.edge.baseQuery) { + predefined = this.edge.baseQuery.listMust( + new es.TermFilter({ field: this.field }) + ); + } - let pushedToBottom = [] - let realCount = 0; - for (let i = 0; i < buckets.length; i++) { - let bucket = buckets[i]; - - // ignore empty strings - if (this.ignoreEmptyString && bucket.key === "") { - continue; - } - - // ignore pre-defined filters - if (this.excludePreDefinedFilters) { - let exclude = false; - for (let j = 0; j < predefined.length; j++) { - let f = predefined[j]; - if (bucket.key === f.value) { - exclude = true; - break; - } - } - if (exclude) { - continue; - } - } - - // if we get to here we're going to add this to the values, so - // increment the real count - realCount++; - - // we must cut off at the set size, as there may be more - // terms that we care about - if (realCount > this.size) { - break; - } - - // translate the term if necessary - let key = this._translate(bucket.key); - - // store the original value and the translated value plus the count - let obj = {display: key, term: bucket.key, count: bucket.doc_count}; - - if (this.pushToBottom.includes(bucket.key)) { - pushedToBottom.push(obj) - } else { - this.values.push(obj); - } + let pushedToBottom = []; + let realCount = 0; + for (let i = 0; i < buckets.length; i++) { + let bucket = buckets[i]; + + // ignore empty strings + if (this.ignoreEmptyString && bucket.key === "") { + continue; + } + + // ignore pre-defined filters + if (this.excludePreDefinedFilters) { + let exclude = false; + for (let j = 0; j < predefined.length; j++) { + let f = predefined[j]; + if (bucket.key === f.value) { + exclude = true; + break; + } } - this.values = this.values.concat(pushedToBottom); - }; - - ///////////////////////////////////////////////// - // query handlers for getting the full list of terms to display - - listAll() { - // to list all possible terms, build off the base query - let bq = this.edge.cloneBaseQuery(); - bq.clearAggregations(); - bq.size = 0; - - // now add the aggregation that we want - let params = { - name: this.id, - field: this.field, - orderBy: this.orderBy, - orderDir: this.orderDir, - size: this.size - }; - bq.addAggregation( - new es.TermsAggregation(params) - ); - - // issue the query to elasticsearch - this.edge.queryAdapter.doQuery({ - edge: this.edge, - query: bq, - success: edges.util.objClosure(this, "listAllQuerySuccess", ["result"]), - error: edges.util.objClosure(this, "listAllQueryFail") - }); - }; - - listAllQuerySuccess(params) { - let result = params.result; - - // set the values according to what comes back - this.values = []; - this._readValues({result: result}); - - // since this happens asynchronously, we may want to draw - this.draw(); - }; - - listAllQueryFail() { - this.values = []; - console.log("RefiningANDTermSelector asynchronous query failed"); - }; - - ////////////////////////////////////////// - // functions that can be called on this component to change its state - - selectTerm(term) { - let nq = this.edge.cloneQuery(); - - // first make sure we're not double-selecting a term - let removeCount = nq.removeMust(new es.TermFilter({ - field: this.field, - value: term - })); - - // all we've done is remove and then re-add the same term, so take no action - if (removeCount > 0) { - return false; + if (exclude) { + continue; } - - // just add a new term filter (the query builder will ensure there are no duplicates) - // this means that the behaviour here is that terms are ANDed together - nq.addMust(new es.TermFilter({ - field: this.field, - value: term - })); - - // reset the search page to the start and then trigger the next query - nq.from = 0; - this.edge.pushQuery(nq); - this.edge.cycle(); - - return true; + } + + // if we get to here we're going to add this to the values, so + // increment the real count + realCount++; + + // we must cut off at the set size, as there may be more + // terms that we care about + if (realCount > this.size) { + break; + } + + // translate the term if necessary + let key = this._translate(bucket.key); + + // store the original value and the translated value plus the count + let obj = { display: key, term: bucket.key, count: bucket.doc_count }; + if (this.pushToBottom.includes(bucket.key)) { + pushedToBottom.push(obj); + } else { + this.values.push(obj); + } + } + this.values = this.values.concat(pushedToBottom); + } + + ///////////////////////////////////////////////// + // query handlers for getting the full list of terms to display + + listAll() { + // to list all possible terms, build off the base query + let bq = this.edge.cloneBaseQuery(); + bq.clearAggregations(); + bq.size = 0; + + // now add the aggregation that we want + let params = { + name: this.id, + field: this.field, + orderBy: this.orderBy, + orderDir: this.orderDir, + size: this.size, }; + bq.addAggregation(new es.TermsAggregation(params)); + + // issue the query to elasticsearch + this.edge.queryAdapter.doQuery({ + edge: this.edge, + query: bq, + success: edges.util.objClosure(this, "listAllQuerySuccess", ["result"]), + error: edges.util.objClosure(this, "listAllQueryFail"), + }); + } + + listAllQuerySuccess(params) { + let result = params.result; + + // set the values according to what comes back + this.values = []; + this._readValues({ result: result }); + + // since this happens asynchronously, we may want to draw + this.draw(); + } + + listAllQueryFail() { + this.values = []; + console.log("RefiningANDTermSelector asynchronous query failed"); + } + + ////////////////////////////////////////// + // functions that can be called on this component to change its state + + selectTerm(term) { + let nq = this.edge.cloneQuery(); + + // first make sure we're not double-selecting a term + let removeCount = nq.removeMust( + new es.TermFilter({ + field: this.field, + value: term, + }) + ); + + // all we've done is remove and then re-add the same term, so take no action + if (removeCount > 0) { + return false; + } - removeFilter(term) { - let nq = this.edge.cloneQuery(); - - nq.removeMust(new es.TermFilter({ + // just add a new term filter (the query builder will ensure there are no duplicates) + // this means that the behaviour here is that terms are ANDed together + nq.addMust( + new es.TermFilter({ + field: this.field, + value: `"${term}"`, + }) + ); + + // reset the search page to the start and then trigger the next query + nq.from = 0; + this.edge.pushQuery(nq); + this.edge.cycle(); + + return true; + } + + removeFilter(term) { + let nq = this.edge.cloneQuery(); + + nq.removeMust( + new es.TermFilter({ + field: this.field, + value: term, + }) + ); + console.log("got term", nq); + // reset the search page to the start and then trigger the next query + nq.from = 0; + this.edge.pushQuery(nq); + this.edge.cycle(); + } + + clearFilters(params) { + let triggerQuery = edges.util.getParam(params, "triggerQuery", true); + + if (this.filters.length > 0) { + let nq = this.edge.cloneQuery(); + for (let i = 0; i < this.filters.length; i++) { + let filter = this.filters[i]; + nq.removeMust( + new es.TermFilter({ field: this.field, - value: term - })); - - // reset the search page to the start and then trigger the next query - nq.from = 0; - this.edge.pushQuery(nq); - this.edge.cycle(); - }; - - clearFilters(params) { - let triggerQuery = edges.util.getParam(params, "triggerQuery", true); - - if (this.filters.length > 0) { - let nq = this.edge.cloneQuery(); - for (let i = 0; i < this.filters.length; i++) { - let filter = this.filters[i]; - nq.removeMust(new es.TermFilter({ - field: this.field, - value: filter.term - })); - } - this.edge.pushQuery(nq); - } - if (triggerQuery) { - this.edge.cycle(); - } - }; - - changeSize(newSize) { - this.size = newSize; - - let nq = this.edge.cloneQuery(); - let agg = nq.getAggregation({ - name: this.id - }); - agg.size = this.size; - this.edge.pushQuery(nq); - this.edge.cycle(); - }; - - changeSort(orderBy, orderDir) { - this.orderBy = orderBy; - this.orderDir = orderDir; - - let nq = this.edge.cloneQuery(); - let agg = nq.getAggregation({ - name: this.id - }); - agg.setOrdering(this.orderBy, this.orderDir); - this.edge.pushQuery(nq); - this.edge.cycle(); - }; - - _translate(term) { - if (this.valueMap) { - if (term in this.valueMap) { - return this.valueMap[term]; - } - } else if (this.valueFunction) { - return this.valueFunction(term); - } - return term; - }; -} \ No newline at end of file + value: filter.term, + }) + ); + } + this.edge.pushQuery(nq); + } + if (triggerQuery) { + this.edge.cycle(); + } + } + + changeSize(newSize) { + this.size = newSize; + + let nq = this.edge.cloneQuery(); + let agg = nq.getAggregation({ + name: this.id, + }); + agg.size = this.size; + this.edge.pushQuery(nq); + this.edge.cycle(); + } + + changeSort(orderBy, orderDir) { + this.orderBy = orderBy; + this.orderDir = orderDir; + + let nq = this.edge.cloneQuery(); + let agg = nq.getAggregation({ + name: this.id, + }); + agg.setOrdering(this.orderBy, this.orderDir); + this.edge.pushQuery(nq); + this.edge.cycle(); + } + + _translate(term) { + if (this.valueMap) { + if (term in this.valueMap) { + return this.valueMap[term]; + } + } else if (this.valueFunction) { + return this.valueFunction(term); + } + return term; + } +}; diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index ae9db19..6acedb6 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -542,6 +542,9 @@ es.Query = class { should: this.should.map((filter) => ({ ...filter })), // Shallow copy of should array minimumShouldMatch: this.minimumShouldMatch, queryString: this.queryString ? { ...this.queryString } : null, // Shallow copy of queryString if present + queryStrings: this.queryStrings + ? this.queryStrings.map((qs) => ({ ...qs })) // Shallow copy of queryStrings array if present + : false, // Default to false if queryStrings is not present sort: this.sort.map((sort) => ({ ...sort })), // Shallow copy of sort array source: this.source ? { @@ -957,7 +960,7 @@ es.querySuccess = function (callback, error_callback) { error_callback(data); return; } - console.log("Got result as", data); + var result = new es.Result({ raw: data }); callback(result); }; diff --git a/src/edges.js b/src/edges.js index ebe4fb3..2d42dec 100644 --- a/src/edges.js +++ b/src/edges.js @@ -1,913 +1,962 @@ // requires: $ // requires: es -if (!window.hasOwnProperty("edges")) { edges = {}} -if (!edges.hasOwnProperty("util")) { edges.util = {}} -if (!edges.hasOwnProperty("es")) { edges.es = {}} +if (!window.hasOwnProperty("edges")) { + edges = {}; +} +if (!edges.hasOwnProperty("util")) { + edges.util = {}; +} +if (!edges.hasOwnProperty("es")) { + edges.es = {}; +} ////////////////////////////////////////////////////////////////// // Main edge class edges.Edge = class { - constructor(params) { - ///////////////////////////////////////////// - // parameters that can be set via params arg - - // the jquery selector for the element where the edge will be deployed - this.selector = edges.util.getParam(params, "selector", "body"); - - // the base search url which will respond to elasticsearch queries. Generally ends with _search - this.searchUrl = edges.util.getParam(params, "searchUrl", false); - - // datatype for ajax requests to use - overall recommend using jsonp and proxying ES requests - // through a back end that can provide that - this.datatype = edges.util.getParam(params, "datatype", "jsonp"); - - // dictionary of queries to be run before the primary query is executed - // {"" : new es.Query(....)} - // results will appear with the same ids in this.preflightResults - // preflight queries are /not/ subject to the base query - this.preflightQueries = edges.util.getParam(params, "preflightQueries", false); - - // query that forms the basis of all queries that are assembled and run - // Note that baseQuery is inviolable - it's requirements will always be enforced - this.baseQuery = edges.util.getParam(params, "baseQuery", false); - - // query to use to initialise the search. Use this to set your opening - // values for things like page size, initial search terms, etc. Any request to - // reset the interface will return to this query - this.openingQuery = edges.util.getParam(params, "openingQuery", () => typeof es !== 'undefined' ? new es.Query() : false); - - // dictionary of functions that will generate secondary queries which also need to be - // run at the point that cycle() is called. These functions and their resulting - // queries will be run /after/ the primary query (so can take advantage of the - // results). Their results will be stored in this.secondaryResults. - // secondary queries are not subject the base query, although the functions - // may of course apply the base query too if they wish - // {"" : function() } - this.secondaryQueries = edges.util.getParam(params, "secondaryQueries", false); - - // dictionary mapping keys to urls that will be used for search. These should be - // the same keys as used in secondaryQueries, if those secondary queries should be - // issued against different urls than the primary search_url. - this.secondaryUrls = edges.util.getParam(params, "secondaryUrls", false); - - // should the init process do a search - this.initialSearch = edges.util.getParam(params, "initialSearch", true); - - // list of static files (e.g. data files) to be loaded at startup, and made available - // on the object for use by components - // {"id" : "", "url" : "", "processor" : edges.csv.newObjectByRow, "datatype" : "text", "opening" : } - this.staticFiles = edges.util.getParam(params, "staticFiles", []); - - // should the search url be synchronised with the browser's url bar after search - // and should queries be retrieved from the url on init - this.manageUrl = edges.util.getParam(params, "manageUrl", false); - - // query parameter in which the query for this edge instance will be stored - this.urlQuerySource = edges.util.getParam(params, "urlQuerySource", "source"); - - // options to be passed to es.Query.objectify when prepping the query to be placed in the URL - this.urlQueryOptions = edges.util.getParam(params, "urlQueryOptions", false); - - // template object that will be used to draw the frame for the edge. May be left - // blank, in which case the edge will assume that the elements are already rendered - // on the page by the caller - this.template = edges.util.getParam(params, "template", false); - - // list of all the components that are involved in this edge - this.components = edges.util.getParam(params, "components", []); + constructor(params) { + ///////////////////////////////////////////// + // parameters that can be set via params arg + + // the jquery selector for the element where the edge will be deployed + this.selector = edges.util.getParam(params, "selector", "body"); + + // the base search url which will respond to elasticsearch queries. Generally ends with _search + this.searchUrl = edges.util.getParam(params, "searchUrl", false); + + // datatype for ajax requests to use - overall recommend using jsonp and proxying ES requests + // through a back end that can provide that + this.datatype = edges.util.getParam(params, "datatype", "jsonp"); + + // dictionary of queries to be run before the primary query is executed + // {"" : new es.Query(....)} + // results will appear with the same ids in this.preflightResults + // preflight queries are /not/ subject to the base query + this.preflightQueries = edges.util.getParam( + params, + "preflightQueries", + false + ); + + // query that forms the basis of all queries that are assembled and run + // Note that baseQuery is inviolable - it's requirements will always be enforced + this.baseQuery = edges.util.getParam(params, "baseQuery", false); + + // query to use to initialise the search. Use this to set your opening + // values for things like page size, initial search terms, etc. Any request to + // reset the interface will return to this query + this.openingQuery = edges.util.getParam(params, "openingQuery", () => + typeof es !== "undefined" ? new es.Query() : false + ); + + // dictionary of functions that will generate secondary queries which also need to be + // run at the point that cycle() is called. These functions and their resulting + // queries will be run /after/ the primary query (so can take advantage of the + // results). Their results will be stored in this.secondaryResults. + // secondary queries are not subject the base query, although the functions + // may of course apply the base query too if they wish + // {"" : function() } + this.secondaryQueries = edges.util.getParam( + params, + "secondaryQueries", + false + ); + + // dictionary mapping keys to urls that will be used for search. These should be + // the same keys as used in secondaryQueries, if those secondary queries should be + // issued against different urls than the primary search_url. + this.secondaryUrls = edges.util.getParam(params, "secondaryUrls", false); + + // should the init process do a search + this.initialSearch = edges.util.getParam(params, "initialSearch", true); + + // list of static files (e.g. data files) to be loaded at startup, and made available + // on the object for use by components + // {"id" : "", "url" : "", "processor" : edges.csv.newObjectByRow, "datatype" : "text", "opening" : } + this.staticFiles = edges.util.getParam(params, "staticFiles", []); + + // should the search url be synchronised with the browser's url bar after search + // and should queries be retrieved from the url on init + this.manageUrl = edges.util.getParam(params, "manageUrl", false); + + // query parameter in which the query for this edge instance will be stored + this.urlQuerySource = edges.util.getParam( + params, + "urlQuerySource", + "source" + ); + + // options to be passed to es.Query.objectify when prepping the query to be placed in the URL + this.urlQueryOptions = edges.util.getParam( + params, + "urlQueryOptions", + false + ); + + // template object that will be used to draw the frame for the edge. May be left + // blank, in which case the edge will assume that the elements are already rendered + // on the page by the caller + this.template = edges.util.getParam(params, "template", false); + + // list of all the components that are involved in this edge + this.components = edges.util.getParam(params, "components", []); + + // the query adapter + this.queryAdapter = edges.util.getParam( + params, + "queryAdapter", + () => new edges.es.ESQueryAdapter() + ); + + // list of callbacks to be run synchronously with the edge instance as the argument + // (these bind at the same points as all the events are triggered, and are keyed the same way) + this.callbacks = edges.util.getParam(params, "callbacks", {}); + + ///////////////////////////////////////////// + // operational properties + + // the query most recently read from the url + this.urlQuery = false; + + // original url parameters + this.urlParams = {}; + + // the short url for this page + this.shortUrl = false; + + // the last primary ES query object that was executed + this.currentQuery = false; + + // the last result object from the ES layer + this.result = false; + + // the results of the preflight queries, keyed by their id + this.preflightResults = {}; + + // the actual secondary queries derived from the functions in this.secondaryQueries; + this.realisedSecondaryQueries = {}; + + // results of the secondary queries, keyed by their id + this.secondaryResults = {}; + + // if the search is currently executing + this.searching = false; + + // jquery object that represents the selected element + this.context = false; + + // raw access to this.staticFiles loaded resources, keyed by id + this.static = {}; + + // access to processed static files, keyed by id + this.resources = {}; + + // list of static resources where errors were encountered + this.errorLoadingStatic = []; + + ////////////////////////////////////////// + // now kick off the edge + this.startup(); + } - // the query adapter - this.queryAdapter = edges.util.getParam(params, "queryAdapter", () => new edges.es.ESQueryAdapter()); + ////////////////////////////////////////////////// + // Startup - // list of callbacks to be run synchronously with the edge instance as the argument - // (these bind at the same points as all the events are triggered, and are keyed the same way) - this.callbacks = edges.util.getParam(params, "callbacks", {}); + startup() { + // obtain the jquery context for all our operations + this.context = $(this.selector); - ///////////////////////////////////////////// - // operational properties + // trigger the edges:init event + this.trigger("edges:pre-init"); - // the query most recently read from the url - this.urlQuery = false; - - // original url parameters - this.urlParams = {}; - - // the short url for this page - this.shortUrl = false; - - // the last primary ES query object that was executed - this.currentQuery = false; - - // the last result object from the ES layer - this.result = false; - - // the results of the preflight queries, keyed by their id - this.preflightResults = {}; - - // the actual secondary queries derived from the functions in this.secondaryQueries; - this.realisedSecondaryQueries = {}; - - // results of the secondary queries, keyed by their id - this.secondaryResults = {}; - - // if the search is currently executing - this.searching = false; - - // jquery object that represents the selected element - this.context = false; - - // raw access to this.staticFiles loaded resources, keyed by id - this.static = {}; - - // access to processed static files, keyed by id - this.resources = {}; - - // list of static resources where errors were encountered - this.errorLoadingStatic = []; - - - ////////////////////////////////////////// - // now kick off the edge - this.startup(); + // if we are to manage the URL, attempt to pull a query from it + if (this.manageUrl) { + var urlParams = edges.util.getUrlParams(); + if (this.urlQuerySource in urlParams) { + this.urlQuery = new es.Query({ raw: urlParams[this.urlQuerySource] }); + delete urlParams[this.urlQuerySource]; + } + this.urlParams = urlParams; } - ////////////////////////////////////////////////// - // Startup - - startup() { - // obtain the jquery context for all our operations - this.context = $(this.selector); - - // trigger the edges:init event - this.trigger("edges:pre-init"); - - // if we are to manage the URL, attempt to pull a query from it - if (this.manageUrl) { - var urlParams = edges.util.getUrlParams(); - if (this.urlQuerySource in urlParams) { - this.urlQuery = new es.Query({raw : urlParams[this.urlQuerySource]}); - delete urlParams[this.urlQuerySource]; - } - this.urlParams = urlParams; - } - - // render the template if necessary - if (this.template) { - this.template.draw(this); - } - - // call each of the components to initialise themselves - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - component.init(this); - } - - // now call each component to render itself (pre-search) - this.draw(); + // render the template if necessary + if (this.template) { + this.template.draw(this); + } - // load any static files - this will happen asynchronously, so afterwards - // we call finaliseStartup to finish the process - // var onward = edges.edges.util.objClosure(this, "startupPart2"); - let onward = () => this.startupPart2() - this.loadStaticsAsync(onward); + // call each of the components to initialise themselves + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + component.init(this); } - startupPart2() { - // FIXME: at this point we should check whether the statics all loaded correctly - // var onward = edges.edges.util.objClosure(this, "startupPart3"); - let onward = () => this.startupPart3() - this.runPreflightQueries(onward); - }; + // now call each component to render itself (pre-search) + this.draw(); + + // load any static files - this will happen asynchronously, so afterwards + // we call finaliseStartup to finish the process + // var onward = edges.edges.util.objClosure(this, "startupPart2"); + let onward = () => this.startupPart2(); + this.loadStaticsAsync(onward); + } + + startupPart2() { + // FIXME: at this point we should check whether the statics all loaded correctly + // var onward = edges.edges.util.objClosure(this, "startupPart3"); + let onward = () => this.startupPart3(); + this.runPreflightQueries(onward); + } + + startupPart3() { + // determine whether to initialise with either the openingQuery or the urlQuery + var requestedQuery = this.openingQuery; + if (this.urlQuery) { + // if there is a URL query, then we open with that, and then forget it + requestedQuery = this.urlQuery; + this.urlQuery = false; + } - startupPart3() { + // request the components to contribute to the query + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + component.contrib(requestedQuery); + } - // determine whether to initialise with either the openingQuery or the urlQuery - var requestedQuery = this.openingQuery; - if (this.urlQuery) { - // if there is a URL query, then we open with that, and then forget it - requestedQuery = this.urlQuery; - this.urlQuery = false - } + // finally push the query, which will reconcile it with the baseQuery + this.pushQuery(requestedQuery); - // request the components to contribute to the query - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - component.contrib(requestedQuery); - } + // trigger the edges:post-init event + this.trigger("edges:post-init"); - // finally push the query, which will reconcile it with the baseQuery - this.pushQuery(requestedQuery); + // now issue a query + this.cycle(); + } - // trigger the edges:post-init event - this.trigger("edges:post-init"); + //////////////////////////////////////////////////// + // Cycle - // now issue a query - this.cycle(); - }; + cycle() { + // if a search is currently executing, don't do anything, else turn it on + // FIXME: should we queue them up? - see the d3 map for an example of how to do this + if (this.searching) { + return; + } + this.searching = true; - //////////////////////////////////////////////////// - // Cycle + // invalidate the short url + this.shortUrl = false; - cycle() { - // if a search is currently executing, don't do anything, else turn it on - // FIXME: should we queue them up? - see the d3 map for an example of how to do this - if (this.searching) { - return; - } - this.searching = true; + // pre query event + this.trigger("edges:pre-query"); - // invalidate the short url - this.shortUrl = false; + // if we are managing the url space, use pushState to set it + if (this.manageUrl) { + this.updateUrl(); + } - // pre query event - this.trigger("edges:pre-query"); + // if there's a search url, do a query, otherwise call synchronise and draw directly + if (this.searchUrl) { + // var onward = edges.edges.util.objClosure(this, "cyclePart2"); + let onward = () => this.cyclePart2(); + this.doPrimaryQuery(onward); + } else { + this.cyclePart2(); + } + } + + cyclePart2() { + // var onward = edges.edges.util.objClosure(this, "cyclePart3"); + let onward = () => this.cyclePart3(); + this.runSecondaryQueries(onward); + } + + cyclePart3() { + this.synchronise(); + + // pre-render trigger + this.trigger("edges:pre-render"); + // render + this.draw(); + // post render trigger + this.trigger("edges:post-render"); + + // searching has completed, so flip the switch back + this.searching = false; + } + + //////////////////////////////////////////////////// + // utilities required during startup + + loadStaticsAsync(callback) { + if (!this.staticFiles || this.staticFiles.length === 0) { + this.trigger("edges:post-load-static"); + callback(); + return; + } - // if we are managing the url space, use pushState to set it - if (this.manageUrl) { - this.updateUrl(); + // FIXME: this could be done with a Promise.all + var that = this; + var pg = new edges.util.AsyncGroup({ + list: this.staticFiles, + action: function (params) { + var entry = params.entry; + var success = params.success_callback; + var error = params.error_callback; + + var id = entry.id; + var url = entry.url; + var datatype = edges.util.getParam(entry.datatype, "text"); + + $.ajax({ + type: "get", + url: url, + dataType: datatype, + success: success, + error: error, + }); + }, + successCallbackArgs: ["data"], + success: function (params) { + var data = params.data; + var entry = params.entry; + if (entry.processor) { + var processed = entry.processor({ data: data }); + that.resources[entry.id] = processed; + if (entry.opening) { + entry.opening({ resource: processed, edge: that }); + } } + that.static[entry.id] = data; + }, + errorCallbackArgs: ["data"], + error: function (params) { + that.errorLoadingStatic.push(params.entry.id); + that.trigger("edges:error-load-static"); + }, + carryOn: function () { + that.trigger("edges:post-load-static"); + callback(); + }, + }); - // if there's a search url, do a query, otherwise call synchronise and draw directly - if (this.searchUrl) { - // var onward = edges.edges.util.objClosure(this, "cyclePart2"); - let onward = () => this.cyclePart2(); - this.doPrimaryQuery(onward); - } else { - this.cyclePart2(); - } - } + pg.process(); + } - cyclePart2() { - // var onward = edges.edges.util.objClosure(this, "cyclePart3"); - let onward = () => this.cyclePart3(); - this.runSecondaryQueries(onward); + runPreflightQueries(callback) { + if ( + !this.preflightQueries || + Object.keys(this.preflightQueries).length === 0 + ) { + callback(); + return; } - cyclePart3() { - this.synchronise(); + this.trigger("edges:pre-preflight"); - // pre-render trigger - this.trigger("edges:pre-render"); - // render - this.draw(); - // post render trigger - this.trigger("edges:post-render"); - - // searching has completed, so flip the switch back - this.searching = false; + var entries = []; + var ids = Object.keys(this.preflightQueries); + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + entries.push({ id: id, query: this.preflightQueries[id] }); } - //////////////////////////////////////////////////// - // utilities required during startup + var that = this; + var pg = new edges.util.AsyncGroup({ + list: entries, + action: function (params) { + var entry = params.entry; + var success = params.success_callback; + var error = params.error_callback; - loadStaticsAsync(callback) { - if (!this.staticFiles || this.staticFiles.length === 0) { - this.trigger("edges:post-load-static"); - callback(); - return; - } - - // FIXME: this could be done with a Promise.all - var that = this; - var pg = new edges.util.AsyncGroup({ - list: this.staticFiles, - action: function(params) { - var entry = params.entry; - var success = params.success_callback; - var error = params.error_callback; - - var id = entry.id; - var url = entry.url; - var datatype = edges.util.getParam(entry.datatype, "text"); - - $.ajax({ - type: "get", - url: url, - dataType: datatype, - success: success, - error: error - }) - }, - successCallbackArgs: ["data"], - success: function(params) { - var data = params.data; - var entry = params.entry; - if (entry.processor) { - var processed = entry.processor({data : data}); - that.resources[entry.id] = processed; - if (entry.opening) { - entry.opening({resource : processed, edge: that}); - } - } - that.static[entry.id] = data; - }, - errorCallbackArgs : ["data"], - error: function(params) { - that.errorLoadingStatic.push(params.entry.id); - that.trigger("edges:error-load-static"); - }, - carryOn: function() { - that.trigger("edges:post-load-static"); - callback(); - } + es.doQuery({ + search_url: that.searchUrl, + queryobj: entry.query.objectify(), + datatype: that.datatype, + success: success, + error: error, }); + }, + successCallbackArgs: ["result"], + success: function (params) { + var result = params.result; + var entry = params.entry; + that.preflightResults[entry.id] = result; + }, + errorCallbackArgs: ["result"], + error: function (params) { + that.trigger("edges:error-preflight"); + }, + carryOn: function () { + that.trigger("edges:post-preflight"); + callback(); + }, + }); - pg.process(); - } + pg.process(); + } - runPreflightQueries(callback) { - if (!this.preflightQueries || Object.keys(this.preflightQueries).length === 0) { - callback(); - return; - } + /////////////////////////////////////////////////// + // Utilities required during cycle - this.trigger("edges:pre-preflight"); + doPrimaryQuery(callback) { + var context = { callback: callback }; - var entries = []; - var ids = Object.keys(this.preflightQueries); - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - entries.push({id: id, query: this.preflightQueries[id]}); - } + this.queryAdapter.doQuery({ + edge: this, + success: edges.util.objClosure(this, "querySuccess", ["result"], context), + error: edges.util.objClosure(this, "queryFail", ["response"], context), + }); + } - var that = this; - var pg = new edges.util.AsyncGroup({ - list: entries, - action: function(params) { - var entry = params.entry; - var success = params.success_callback; - var error = params.error_callback; - - es.doQuery({ - search_url: that.searchUrl, - queryobj: entry.query.objectify(), - datatype: that.datatype, - success: success, - error: error - }); - }, - successCallbackArgs: ["result"], - success: function(params) { - var result = params.result; - var entry = params.entry; - that.preflightResults[entry.id] = result; - }, - errorCallbackArgs : ["result"], - error: function(params) { - that.trigger("edges:error-preflight"); - }, - carryOn: function() { - that.trigger("edges:post-preflight"); - callback(); - } - }); + runSecondaryQueries(callback) { + this.realisedSecondaryQueries = {}; - pg.process(); + if ( + !this.secondaryQueries || + Object.keys(this.secondaryQueries).length === 0 + ) { + callback(); + return; } - /////////////////////////////////////////////////// - // Utilities required during cycle - - doPrimaryQuery(callback) { - var context = {"callback" : callback}; - - this.queryAdapter.doQuery({ - edge: this, - success: edges.util.objClosure(this, "querySuccess", ["result"], context), - error: edges.util.objClosure(this, "queryFail", ["response"], context) - }); + // generate the query objects to be executed + var entries = []; + for (var key in this.secondaryQueries) { + var entry = {}; + entry["query"] = this.secondaryQueries[key](this); + entry["id"] = key; + entry["searchUrl"] = this.searchUrl; + if ( + this.secondaryUrls !== false && + this.secondaryUrls.hasOwnProperty(key) + ) { + entry["searchUrl"] = this.secondaryUrls[key]; + } + entries.push(entry); + this.realisedSecondaryQueries[key] = entry.query; } - runSecondaryQueries(callback) { - this.realisedSecondaryQueries = {}; - if (!this.secondaryQueries || Object.keys(this.secondaryQueries).length === 0) { - callback(); - return; - } + var that = this; + var pg = new edges.util.AsyncGroup({ + list: entries, + action: function (params) { + var entry = params.entry; + var success = params.success_callback; + var error = params.error_callback; - // generate the query objects to be executed - var entries = []; - for (var key in this.secondaryQueries) { - var entry = {}; - entry["query"] = this.secondaryQueries[key](this); - entry["id"] = key; - entry["searchUrl"] = this.searchUrl; - if (this.secondaryUrls !== false && this.secondaryUrls.hasOwnProperty(key)) { - entry["searchUrl"] = this.secondaryUrls[key] - } - entries.push(entry); - this.realisedSecondaryQueries[key] = entry.query; - } - - var that = this; - var pg = new edges.util.AsyncGroup({ - list: entries, - action: function(params) { - var entry = params.entry; - var success = params.success_callback; - var error = params.error_callback; - - es.doQuery({ - search_url: entry.searchUrl, - queryobj: entry.query.objectify(), - datatype: that.datatype, - success: success, - complete: false - }); - }, - successCallbackArgs: ["result"], - success: function(params) { - var result = params.result; - var entry = params.entry; - that.secondaryResults[entry.id] = result; - }, - errorCallbackArgs : ["result"], - error: function(params) { - // FIXME: not really sure what to do about this - }, - carryOn: function() { - callback(); - } + es.doQuery({ + search_url: entry.searchUrl, + queryobj: entry.query.objectify(), + datatype: that.datatype, + success: success, + complete: false, }); + }, + successCallbackArgs: ["result"], + success: function (params) { + var result = params.result; + var entry = params.entry; + that.secondaryResults[entry.id] = result; + }, + errorCallbackArgs: ["result"], + error: function (params) { + // FIXME: not really sure what to do about this + }, + carryOn: function () { + callback(); + }, + }); - pg.process(); - } + pg.process(); + } - //////////////////////////////////////////////////// - // functions for working with the queries + //////////////////////////////////////////////////// + // functions for working with the queries - cloneQuery() { - if (this.currentQuery) { - return this.currentQuery.clone(); - } - return false; + cloneQuery() { + if (this.currentQuery) { + console.log("Current query", this.currentQuery); + return this.currentQuery.clone(); } + return false; + } - pushQuery(query) { - if (this.baseQuery) { - query.merge(this.baseQuery); - } - this.currentQuery = query; + pushQuery(query) { + if (this.baseQuery) { + query.merge(this.baseQuery); } + this.currentQuery = query; + } - cloneBaseQuery() { - if (this.baseQuery) { - return this.baseQuery.clone(); - } - return new es.Query(); + cloneBaseQuery() { + if (this.baseQuery) { + return this.baseQuery.clone(); } + return new es.Query(); + } - cloneOpeningQuery() { - if (this.openingQuery) { - return this.openingQuery.clone(); - } - return new es.Query(); + cloneOpeningQuery() { + if (this.openingQuery) { + return this.openingQuery.clone(); } + return new es.Query(); + } + + queryFail(params) { + var callback = params.callback; + var response = params.response; + this.trigger("edges:query-fail"); + if (response.hasOwnProperty("responseText")) { + console.log("ERROR: query fail: " + response.responseText); + } + if (response.hasOwnProperty("error")) { + console.log("ERROR: search execution fail: " + response.error); + } + callback(); + } - queryFail(params) { - var callback = params.callback; - var response = params.response; - this.trigger("edges:query-fail"); - if (response.hasOwnProperty("responseText")) { - console.log("ERROR: query fail: " + response.responseText); - } - if (response.hasOwnProperty("error")) { - console.log("ERROR: search execution fail: " + response.error); - } - callback(); - }; - - querySuccess(params) { - this.result = params.result; - var callback = params.callback; - - // success trigger - this.trigger("edges:query-success"); - callback(); - }; - - ////////////////////////////////////////////////// - // URL Management + querySuccess(params) { + this.result = params.result; + var callback = params.callback; - updateUrl() { - var currentQs = window.location.search; - var qs = "?" + this.fullUrlQueryString(); + // success trigger + this.trigger("edges:query-success"); + callback(); + } - if (currentQs === qs) { - return; // no need to push the state - } + ////////////////////////////////////////////////// + // URL Management - var url = new URL(window.location.href); - url.search = qs; + updateUrl() { + var currentQs = window.location.search; + var qs = "?" + this.fullUrlQueryString(); - if (currentQs === "") { - window.history.replaceState("", "", url.toString()); - } else { - window.history.pushState("", "", url.toString()); - } + if (currentQs === qs) { + return; // no need to push the state } - fullUrl() { - var args = this.fullQueryArgs(); - var fragment = ""; - if (args["#"]) { - fragment = "#" + args["#"]; - delete args["#"]; - } - var wloc = window.location.toString(); - var bits = wloc.split("?"); - var url = bits[0] + "?" + this._makeUrlQuery(args) + fragment; - return url; - }; + var url = new URL(window.location.href); + url.search = qs; - fullUrlQueryString() { - return this._makeUrlQuery(this.fullQueryArgs()) + if (currentQs === "") { + window.history.replaceState("", "", url.toString()); + } else { + window.history.pushState("", "", url.toString()); } - - fullQueryArgs() { - var args = $.extend(true, {}, this.urlParams); - $.extend(args, this.urlQueryArg()); - return args; - }; - - urlQueryArg(objectify_options) { - if (!objectify_options) { - if (this.urlQueryOptions) { - objectify_options = this.urlQueryOptions - } else { - objectify_options = { - include_query_string : true, - include_filters : true, - include_paging : true, - include_sort : true, - include_fields : false, - include_aggregations : false - } - } - } - var q = JSON.stringify(this.currentQuery.objectify(objectify_options)); - var obj = {}; - obj[this.urlQuerySource] = encodeURIComponent(q); - return obj; + } + + fullUrl() { + var args = this.fullQueryArgs(); + var fragment = ""; + if (args["#"]) { + fragment = "#" + args["#"]; + delete args["#"]; } - - _makeUrlQuery(args) { - var keys = Object.keys(args); - var entries = []; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var val = args[key]; - entries.push(key + "=" + val); // NOTE we do not escape - this should already be done - } - return entries.join("&"); + var wloc = window.location.toString(); + var bits = wloc.split("?"); + var url = bits[0] + "?" + this._makeUrlQuery(args) + fragment; + return url; + } + + fullUrlQueryString() { + return this._makeUrlQuery(this.fullQueryArgs()); + } + + fullQueryArgs() { + var args = $.extend(true, {}, this.urlParams); + $.extend(args, this.urlQueryArg()); + return args; + } + + urlQueryArg(objectify_options) { + if (!objectify_options) { + if (this.urlQueryOptions) { + objectify_options = this.urlQueryOptions; + } else { + objectify_options = { + include_query_string: true, + include_filters: true, + include_paging: true, + include_sort: true, + include_fields: false, + include_aggregations: false, + }; + } } - - ///////////////////////////////////////////////// - // lifecycle functions - - synchronise() { - // ask the components to synchronise themselves with the latest state - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - component.synchronise() - } + var q = JSON.stringify(this.currentQuery.objectify(objectify_options)); + var obj = {}; + obj[this.urlQuerySource] = encodeURIComponent(q); + return obj; + } + + _makeUrlQuery(args) { + var keys = Object.keys(args); + var entries = []; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var val = args[key]; + entries.push(key + "=" + val); // NOTE we do not escape - this should already be done } + return entries.join("&"); + } - draw() { - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - component.draw(this); - } - }; - - reset() { - // tell the world we're about to reset - this.trigger("edges:pre-reset"); + ///////////////////////////////////////////////// + // lifecycle functions - // clone from the opening query - var requestedQuery = this.cloneOpeningQuery(); + synchronise() { + // ask the components to synchronise themselves with the latest state + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + component.synchronise(); + } + } - // request the components to contribute to the query - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - component.contrib(requestedQuery); - } + draw() { + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + component.draw(this); + } + } - // push the query, which will reconcile it with the baseQuery - this.pushQuery(requestedQuery); + reset() { + // tell the world we're about to reset + this.trigger("edges:pre-reset"); - // tell the world that we've done the reset - this.trigger("edges:post-reset"); + // clone from the opening query + var requestedQuery = this.cloneOpeningQuery(); - // now execute the query - // this.doQuery(); - this.cycle(); - }; + // request the components to contribute to the query + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + component.contrib(requestedQuery); + } - sleep() { - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - component.sleep(); - } - }; + // push the query, which will reconcile it with the baseQuery + this.pushQuery(requestedQuery); - wake() { - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - component.wake(); - } - }; + // tell the world that we've done the reset + this.trigger("edges:post-reset"); - trigger(event_name) { - if (event_name in this.callbacks) { - this.callbacks[event_name](this); - } - this.context.trigger(event_name); - }; + // now execute the query + // this.doQuery(); + this.cycle(); + } - //////////////////////////////////////////// - // accessors + sleep() { + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + component.sleep(); + } + } - getComponent(params) { - var id = params.id; - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - if (component.id === id) { - return component; - } - } - return false; - }; + wake() { + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + component.wake(); + } + } - // return components in the requested category - category(cat) { - var comps = []; - for (var i = 0; i < this.components.length; i++) { - var component = this.components[i]; - if (component.category === cat) { - comps.push(component); - } - } - return comps; - }; + trigger(event_name) { + if (event_name in this.callbacks) { + this.callbacks[event_name](this); + } + this.context.trigger(event_name); + } + + //////////////////////////////////////////// + // accessors + + getComponent(params) { + var id = params.id; + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + if (component.id === id) { + return component; + } + } + return false; + } + + // return components in the requested category + category(cat) { + var comps = []; + for (var i = 0; i < this.components.length; i++) { + var component = this.components[i]; + if (component.category === cat) { + comps.push(component); + } + } + return comps; + } - jq(selector) { - return $(selector, this.context); - }; -} + jq(selector) { + return $(selector, this.context); + } +}; ////////////////////////////////////////////////////////////////// // Framework superclasses edges.QueryAdapter = class { - doQuery(params) {}; -} + doQuery(params) {} +}; edges.Template = class { - draw(edge) {} -} + draw(edge) {} +}; edges.Component = class { - constructor(params) { - this.id = edges.util.getParam(params, "id"); - this.renderer = edges.util.getParam(params, "renderer"); - this.category = edges.util.getParam(params, "category", false); + constructor(params) { + this.id = edges.util.getParam(params, "id"); + this.renderer = edges.util.getParam(params, "renderer"); + this.category = edges.util.getParam(params, "category", false); + } + + init(edge) { + this.edge = edge; + this.context = this.edge.jq("#" + this.id); + if (this.renderer) { + this.renderer.init(this); } + } - init(edge) { - this.edge = edge; - this.context = this.edge.jq("#" + this.id); - if (this.renderer) { - this.renderer.init(this); - } + draw() { + if (this.renderer) { + this.renderer.draw(); } + } - draw() { - if (this.renderer) { - this.renderer.draw(); - } + sleep() { + if (this.renderer) { + this.renderer.sleep(); } + } - sleep() { - if (this.renderer) { - this.renderer.sleep(); - } + wake() { + if (this.renderer) { + this.renderer.wake(); } + } - wake() { - if (this.renderer) { - this.renderer.wake(); - } - }; + // convenience method for any renderer rendering a component + jq(selector) { + return this.edge.jq(selector); + } - // convenience method for any renderer rendering a component - jq(selector) { - return this.edge.jq(selector); - } - - // methods to be implemented by subclasses - contrib(query) {} - synchronise() {} -} + // methods to be implemented by subclasses + contrib(query) {} + synchronise() {} +}; edges.Renderer = class { - init(component) { - this.component = component - } + init(component) { + this.component = component; + } - draw() {}; - sleep() {}; - wake() {} -} + draw() {} + sleep() {} + wake() {} +}; ////////////////////////////////////////////////////////////////// // Event binding utilities -edges.on = function(selector, event, caller, targetFunction, delay, conditional, preventDefault) { - if (preventDefault === undefined) { - preventDefault = true; - } - // if the caller has an inner component (i.e. it is a Renderer), use the component's id - // otherwise, if it has a namespace (which is true of Renderers or Templates) use that - if (caller.component && caller.component.id) { - event = event + "." + caller.component.id; - } else if (caller.namespace) { - event = event + "." + caller.namespace; - } - - // create the closure to be called on the event - var clos = edges.util.eventClosure(caller, targetFunction, conditional, preventDefault); - - if (delay) { - clos = edges.util.delayer(clos, delay); - } - - // now bind the closure directly or with delay - // if the caller has an inner component (i.e. it is a Renderer) use the components jQuery selector - // otherwise, if it has an inner, use the selector on that. - // if (delay) { - // if (caller.component) { - // caller.component.jq(selector).bindWithDelay(event, clos, delay); - // } else if (caller.edge) { - // caller.edge.jq(selector).bindWithDelay(event, clos, delay); - // } else { - // $(selector).bindWithDelay(event, clos, delay); - // } - // } else { - if (caller.component) { - var element = caller.component.jq(selector); - element.off(event); - element.on(event, clos); - } else if (caller.edge) { - var element = caller.edge.jq(selector); - element.off(event); - element.on(event, clos); - } else { - var element = $(selector); - element.off(event); - element.on(event, clos); - } - // } -} - -edges.off = function(selector, event, caller) { - // if the caller has an inner component (i.e. it is a Renderer), use the component's id - // otherwise, if it has a namespace (which is true of Renderers or Templates) use that - if (caller.component && caller.component.id) { - event = event + "." + caller.component.id; - } else if (caller.namespace) { - event = event + "." + caller.namespace; - } - - if (caller.component) { - var element = caller.component.jq(selector); - element.off(event); - } else if (caller.edge) { - var element = caller.edge.jq(selector); - element.off(event); - } else { - var element = $(selector); - element.off(event); - } -} +edges.on = function ( + selector, + event, + caller, + targetFunction, + delay, + conditional, + preventDefault +) { + if (preventDefault === undefined) { + preventDefault = true; + } + // if the caller has an inner component (i.e. it is a Renderer), use the component's id + // otherwise, if it has a namespace (which is true of Renderers or Templates) use that + if (caller.component && caller.component.id) { + event = event + "." + caller.component.id; + } else if (caller.namespace) { + event = event + "." + caller.namespace; + } + + // create the closure to be called on the event + var clos = edges.util.eventClosure( + caller, + targetFunction, + conditional, + preventDefault + ); + + if (delay) { + clos = edges.util.delayer(clos, delay); + } + + // now bind the closure directly or with delay + // if the caller has an inner component (i.e. it is a Renderer) use the components jQuery selector + // otherwise, if it has an inner, use the selector on that. + // if (delay) { + // if (caller.component) { + // caller.component.jq(selector).bindWithDelay(event, clos, delay); + // } else if (caller.edge) { + // caller.edge.jq(selector).bindWithDelay(event, clos, delay); + // } else { + // $(selector).bindWithDelay(event, clos, delay); + // } + // } else { + if (caller.component) { + var element = caller.component.jq(selector); + element.off(event); + element.on(event, clos); + } else if (caller.edge) { + var element = caller.edge.jq(selector); + element.off(event); + element.on(event, clos); + } else { + var element = $(selector); + element.off(event); + element.on(event, clos); + } + // } +}; + +edges.off = function (selector, event, caller) { + // if the caller has an inner component (i.e. it is a Renderer), use the component's id + // otherwise, if it has a namespace (which is true of Renderers or Templates) use that + if (caller.component && caller.component.id) { + event = event + "." + caller.component.id; + } else if (caller.namespace) { + event = event + "." + caller.namespace; + } + + if (caller.component) { + var element = caller.component.jq(selector); + element.off(event); + } else if (caller.edge) { + var element = caller.edge.jq(selector); + element.off(event); + } else { + var element = $(selector); + element.off(event); + } +}; ////////////////////////////////////////////////////////////////// // Common/default implementations of framework classes edges.es.ESQueryAdapter = class extends edges.QueryAdapter { - doQuery(params) { - var edge = params.edge; - var query = params.query; - var success = params.success; - var error = params.error; - - if (!query) { - query = edge.currentQuery; - } + doQuery(params) { + var edge = params.edge; + var query = params.query; + var success = params.success; + var error = params.error; + + if (!query) { + query = edge.currentQuery; + } - es.doQuery({ - search_url: edge.searchUrl, - queryobj: query.objectify(), - datatype: edge.datatype, - success: success, - error: error - }); - }; -} + es.doQuery({ + search_url: edge.searchUrl, + queryobj: query.objectify(), + datatype: edge.datatype, + success: success, + error: error, + }); + } +}; // Solr query adapter edges.es.SolrQueryAdapter = class extends edges.QueryAdapter { - doQuery(params) { - var edge = params.edge; - var query = params.query; - var success = params.success; - var error = params.error; - - if (!query) { - query = edge.currentQuery; - } + doQuery(params) { + var edge = params.edge; + var query = params.query; + var success = params.success; + var error = params.error; + + if (!query) { + query = edge.currentQuery; + } - es.doQuery({ - search_url: edge.searchUrl, - query: query, - datatype: edge.datatype, - success: success, - error: error - }) - }; -} + es.doQuery({ + search_url: edge.searchUrl, + query: query, + datatype: edge.datatype, + success: success, + error: error, + }); + } +}; ////////////////////////////////////////////////////////////////// // utilities -edges.util.getParam = function(params, key, def) { - function _getDefault() { - if (typeof def === 'function') { - return def(); - } - return def; +edges.util.getParam = function (params, key, def) { + function _getDefault() { + if (typeof def === "function") { + return def(); } - - if (!params) { - return _getDefault(); + return def; + } + + if (!params) { + return _getDefault(); + } + + if (!params.hasOwnProperty(key)) { + return _getDefault(); + } + + return params[key]; +}; + +edges.util.getUrlParams = function () { + var params = {}; + var url = window.location.href; + var fragment = false; + + // break the anchor off the url + if (url.indexOf("#") > -1) { + fragment = url.slice(url.indexOf("#")); + url = url.substring(0, url.indexOf("#")); + } + + // extract and split the query args + var args = url.slice(url.indexOf("?") + 1).split("&"); + + for (var i = 0; i < args.length; i++) { + var kv = args[i].split("="); + if (kv.length === 2) { + var key = kv[0].replace(/\+/g, "%20"); + key = decodeURIComponent(key); + var val = kv[1].replace(/\+/g, "%20"); + val = decodeURIComponent(val); + if (val[0] === "[" || val[0] === "{") { + // if it looks like a JSON object in string form... + // remove " (double quotes) at beginning and end of string to make it a valid + // representation of a JSON object, or the parser will complain + val = val.replace(/^"/, "").replace(/"$/, ""); + val = JSON.parse(val); + } + params[key] = val; } + } - if (!params.hasOwnProperty(key)) { - return _getDefault(); - } + // record the fragment identifier if required + if (fragment) { + params["#"] = fragment; + } - return params[key]; -} + return params; +}; -edges.util.getUrlParams = function() { - var params = {}; - var url = window.location.href; - var fragment = false; - - // break the anchor off the url - if (url.indexOf("#") > -1) { - fragment = url.slice(url.indexOf('#')); - url = url.substring(0, url.indexOf('#')); - } - - // extract and split the query args - var args = url.slice(url.indexOf('?') + 1).split('&'); - - for (var i = 0; i < args.length; i++) { - var kv = args[i].split('='); - if (kv.length === 2) { - var key = kv[0].replace(/\+/g, "%20"); - key = decodeURIComponent(key); - var val = kv[1].replace(/\+/g, "%20"); - val = decodeURIComponent(val); - if (val[0] === "[" || val[0] === "{") { - // if it looks like a JSON object in string form... - // remove " (double quotes) at beginning and end of string to make it a valid - // representation of a JSON object, or the parser will complain - val = val.replace(/^"/,"").replace(/"$/,""); - val = JSON.parse(val); - } - params[key] = val; - } - } - - // record the fragment identifier if required - if (fragment) { - params['#'] = fragment; - } - - return params; -} - -edges.util.isEmptyObject = function(obj) { - for(var key in obj) { - if(obj.hasOwnProperty(key)) - return false; - } - return true; -} +edges.util.isEmptyObject = function (obj) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) return false; + } + return true; +}; ////////////////////////////////////////////////////////////////// // Closures for integrating the object with other modules @@ -932,351 +981,378 @@ edges.util.isEmptyObject = function(obj) { // results in a call to // this.function({one: arg1, two: arg2}) // -edges.util.objClosure = function(obj, fn, args, context_params) { - return function() { - if (args) { - var params = {}; - for (var i = 0; i < args.length; i++) { - if (arguments.length > i) { - params[args[i]] = arguments[i]; - } - } - if (context_params) { - params = $.extend(params, context_params); - } - obj[fn](params); - } else { - var slice = Array.prototype.slice; - var theArgs = slice.apply(arguments); - if (context_params) { - theArgs.push(context_params); - } - obj[fn].apply(obj, theArgs); +edges.util.objClosure = function (obj, fn, args, context_params) { + return function () { + if (args) { + var params = {}; + for (var i = 0; i < args.length; i++) { + if (arguments.length > i) { + params[args[i]] = arguments[i]; } + } + if (context_params) { + params = $.extend(params, context_params); + } + obj[fn](params); + } else { + var slice = Array.prototype.slice; + var theArgs = slice.apply(arguments); + if (context_params) { + theArgs.push(context_params); + } + obj[fn].apply(obj, theArgs); } -} - -edges.util.eventClosure = function(obj, fn, conditional, preventDefault) { - if (preventDefault === undefined) { - preventDefault = true; + }; +}; + +edges.util.eventClosure = function (obj, fn, conditional, preventDefault) { + if (preventDefault === undefined) { + preventDefault = true; + } + return function (event) { + if (conditional) { + if (!conditional(event)) { + return; + } } - return function(event) { - if (conditional) { - if (!conditional(event)) { - return; - } - } - if (preventDefault) { - event.preventDefault(); - } - obj[fn](event.currentTarget, event); + if (preventDefault) { + event.preventDefault(); } -} - -edges.util.delayer = function(fn, delay, timeout) { - let wait = null; - - return function(event) { - // var e = $.extend(true, { }, arguments[0]); - var throttler = function() { - wait = null; - fn(event); - }; + obj[fn](event.currentTarget, event); + }; +}; + +edges.util.delayer = function (fn, delay, timeout) { + let wait = null; + + return function (event) { + // var e = $.extend(true, { }, arguments[0]); + var throttler = function () { + wait = null; + fn(event); + }; - if (!timeout) { clearTimeout(wait); } - if (!timeout || !wait) { wait = setTimeout(throttler, delay); } + if (!timeout) { + clearTimeout(wait); } -} + if (!timeout || !wait) { + wait = setTimeout(throttler, delay); + } + }; +}; /////////////////////////////////////////////////// // Group of asynchronous operations edges.util.AsyncGroup = class { - constructor(params) { - this.list = edges.util.getParam(params, "list"); - this.successCallbackArgs = edges.util.getParam(params, "successCallbackArgs"); - this.errorCallbackArgs = edges.util.getParam(params, "errorCallbackArgs"); - - this.functions = { - action: edges.util.getParam(params, "action"), - success: edges.util.getParam(params, "success"), - carryOn: edges.util.getParam(params, "carryOn"), - error: edges.util.getParam(params, "error") - }; + constructor(params) { + this.list = edges.util.getParam(params, "list"); + this.successCallbackArgs = edges.util.getParam( + params, + "successCallbackArgs" + ); + this.errorCallbackArgs = edges.util.getParam(params, "errorCallbackArgs"); + + this.functions = { + action: edges.util.getParam(params, "action"), + success: edges.util.getParam(params, "success"), + carryOn: edges.util.getParam(params, "carryOn"), + error: edges.util.getParam(params, "error"), + }; - this.checkList = []; + this.checkList = []; - this.finished = false; + this.finished = false; - for (let i = 0; i < this.list.length; i++) { - this.checkList.push(0); - } + for (let i = 0; i < this.list.length; i++) { + this.checkList.push(0); } + } - process(params) { - if (this.list.length === 0) { - this.functions.carryOn(); - } - - for (let i = 0; i < this.list.length; i++) { - let context = {index: i}; - - let success_callback = edges.util.objClosure(this, "_actionSuccess", this.successCallbackArgs, context); - let error_callback = edges.util.objClosure(this, "_actionError", this.successCallbackArgs, context); - let complete_callback = false; + process(params) { + if (this.list.length === 0) { + this.functions.carryOn(); + } - this.functions.action({entry: this.list[i], - success_callback: success_callback, - error_callback: error_callback, - complete_callback: complete_callback - }); - } - }; + for (let i = 0; i < this.list.length; i++) { + let context = { index: i }; + + let success_callback = edges.util.objClosure( + this, + "_actionSuccess", + this.successCallbackArgs, + context + ); + let error_callback = edges.util.objClosure( + this, + "_actionError", + this.successCallbackArgs, + context + ); + let complete_callback = false; + + this.functions.action({ + entry: this.list[i], + success_callback: success_callback, + error_callback: error_callback, + complete_callback: complete_callback, + }); + } + } - _actionSuccess(params) { - let index = params.index; - delete params.index; + _actionSuccess(params) { + let index = params.index; + delete params.index; - params["entry"] = this.list[index]; - this.functions.success(params); - this.checkList[index] = 1; + params["entry"] = this.list[index]; + this.functions.success(params); + this.checkList[index] = 1; - if (this._isComplete()) { - this._finalise(); - } - }; + if (this._isComplete()) { + this._finalise(); + } + } - _actionError(params) { - let index = params.index; - delete params.index; + _actionError(params) { + let index = params.index; + delete params.index; - params["entry"] = this.list[index]; - this.functions.error(params); - this.checkList[index] = -1; + params["entry"] = this.list[index]; + this.functions.error(params); + this.checkList[index] = -1; - if (this._isComplete()) { - this._finalise(); - } - }; + if (this._isComplete()) { + this._finalise(); + } + } - _actionComplete(params) {}; + _actionComplete(params) {} - _isComplete() { - return $.inArray(0, this.checkList) === -1; - }; + _isComplete() { + return $.inArray(0, this.checkList) === -1; + } - _finalise = function() { - if (this.finished) { - return; - } - this.finished = true; - this.functions.carryOn(); - }; -} + _finalise = function () { + if (this.finished) { + return; + } + this.finished = true; + this.functions.carryOn(); + }; +}; /////////////////////////////////////////////////// // Style/CSS/HTML ID related functions -edges.util.bem = function(block, element, modifier) { - let bemClass = block; - if (element) { - bemClass += "__" + element; - } - if (modifier) { - bemClass += "--" + modifier; - } - return bemClass; -} - -edges.util.styleClasses = function(namespace, field, instance_name) { - instance_name = edges.util._normaliseInstanceName(instance_name); - let cl = namespace; +edges.util.bem = function (block, element, modifier) { + let bemClass = block; + if (element) { + bemClass += "__" + element; + } + if (modifier) { + bemClass += "--" + modifier; + } + return bemClass; +}; + +edges.util.styleClasses = function (namespace, field, instance_name) { + instance_name = edges.util._normaliseInstanceName(instance_name); + let cl = namespace; + if (field) { + cl += "_" + field; + } + if (instance_name) { + let second = namespace + "_" + instance_name; if (field) { - cl += "_" + field - } - if (instance_name) { - let second = namespace + "_" + instance_name; - if (field) { - second += "_" + field; - } - cl += " " + second; + second += "_" + field; } - return cl; -} - -edges.util.jsClasses = function(namespace, field, instance_name) { - instance_name = edges.util._normaliseInstanceName(instance_name); - let styles = edges.util.styleClasses(namespace, field, instance_name) - let jsClasses = ""; - let bits = styles.split(" ") - for (let i = 0; i < bits.length; i++) { - let bit = bits[i]; - jsClasses += " js-" + bit; - } - return jsClasses; -} + cl += " " + second; + } + return cl; +}; + +edges.util.jsClasses = function (namespace, field, instance_name) { + instance_name = edges.util._normaliseInstanceName(instance_name); + let styles = edges.util.styleClasses(namespace, field, instance_name); + let jsClasses = ""; + let bits = styles.split(" "); + for (let i = 0; i < bits.length; i++) { + let bit = bits[i]; + jsClasses += " js-" + bit; + } + return jsClasses; +}; + +edges.util.allClasses = function (namespace, field, instance_name) { + instance_name = edges.util._normaliseInstanceName(instance_name); + let styles = edges.util.styleClasses(namespace, field, instance_name); + let js = edges.util.jsClasses(namespace, field, instance_name); + return styles + " " + js; +}; + +edges.util.jsClassSelector = function (namespace, field, instance_name) { + instance_name = edges.util._normaliseInstanceName(instance_name); + let sel = ".js-" + namespace; + if (instance_name) { + sel += "_" + instance_name; + } + if (field) { + sel += "_" + field; + } + return sel; +}; + +edges.util.htmlID = function (namespace, field, instance_name) { + instance_name = edges.util._normaliseInstanceName(instance_name); + let id = namespace; + if (instance_name) { + id += "_" + instance_name; + } + if (field) { + id += "_" + field; + } + return id; +}; + +edges.util.idSelector = function (namespace, field, instance_name) { + instance_name = edges.util._normaliseInstanceName(instance_name); + return "#" + edges.util.htmlID(namespace, field, instance_name); +}; + +edges.util._normaliseInstanceName = function (instance_name) { + if (typeof instance_name === "string") { + return instance_name; + } + + if (instance_name instanceof edges.Component) { + return instance_name.id; + } + + if (instance_name instanceof edges.Renderer) { + return instance_name.component.id; + } +}; -edges.util.allClasses = function(namespace, field, instance_name) { - instance_name = edges.util._normaliseInstanceName(instance_name); - let styles = edges.util.styleClasses(namespace, field, instance_name); - let js = edges.util.jsClasses(namespace, field, instance_name); - return styles + " " + js; -} +//////////////////////////////////////////////////// +// content wrangling -edges.util.jsClassSelector = function(namespace, field, instance_name) { - instance_name = edges.util._normaliseInstanceName(instance_name); - let sel = ".js-" + namespace; - if (instance_name) { - sel += "_" + instance_name; +edges.util.escapeHtml = function (unsafe, def) { + if (def === undefined) { + def = ""; + } + if (unsafe === undefined || unsafe == null) { + return def; + } + try { + if (typeof unsafe.replace !== "function") { + return unsafe; } - if (field) { - sel += "_" + field; + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } catch (err) { + return def; + } +}; + +edges.util.toHtmlEntities = function (str) { + return str.replace(/./gm, function (s) { + // return "&#" + s.charCodeAt(0) + ";"; + return s.match(/[a-z0-9\s]+/i) ? s : "&#" + s.charCodeAt(0) + ";"; + }); +}; + +edges.util.fromHtmlEntities = function (string) { + return (string + "").replace(/&#\d+;/gm, function (s) { + return String.fromCharCode(s.match(/\d+/gm)[0]); + }); +}; + +edges.util.safeId = function (unsafe) { + return unsafe + .replace(/&/g, "_") + .replace(//g, "_") + .replace(/"/g, "_") + .replace(/'/g, "_") + .replace(/\./gi, "_") + .replace(/\:/gi, "_") + .replace(/\s/gi, "_"); +}; + +edges.util.numFormat = function (params) { + var reflectNonNumbers = edges.util.getParam( + params, + "reflectNonNumbers", + false + ); + var prefix = edges.util.getParam(params, "prefix", ""); + var zeroPadding = edges.util.getParam(params, "zeroPadding", false); + var decimalPlaces = edges.util.getParam(params, "decimalPlaces", false); + var thousandsSeparator = edges.util.getParam( + params, + "thousandsSeparator", + false + ); + var decimalSeparator = edges.util.getParam(params, "decimalSeparator", "."); + var suffix = edges.util.getParam(params, "suffix", ""); + + return function (number) { + // ensure this is really a number + var num = parseFloat(number); + if (isNaN(num)) { + if (reflectNonNumbers) { + return number; + } else { + return num; + } } - return sel; -} -edges.util.htmlID = function(namespace, field, instance_name) { - instance_name = edges.util._normaliseInstanceName(instance_name); - let id = namespace; - if (instance_name) { - id += "_" + instance_name; - } - if (field) { - id += "_" + field; + // first off we need to convert the number to a string, which we can do directly, or using toFixed if that + // is suitable here + if (decimalPlaces !== false) { + num = num.toFixed(decimalPlaces); + } else { + num = num.toString(); } - return id; -} -edges.util.idSelector = function(namespace, field, instance_name) { - instance_name = edges.util._normaliseInstanceName(instance_name); - return "#" + edges.util.htmlID(namespace, field, instance_name); -} + // now "num" is a string containing the formatted number that we can work on -edges.util._normaliseInstanceName = function(instance_name) { - if (typeof instance_name === "string") { - return instance_name; - } - - if (instance_name instanceof edges.Component) { - return instance_name.id; - } + var bits = num.split("."); - if (instance_name instanceof edges.Renderer) { - return instance_name.component.id; + if (zeroPadding !== false) { + var zeros = zeroPadding - bits[0].length; + var pad = ""; + for (var i = 0; i < zeros; i++) { + pad += "0"; + } + bits[0] = pad + bits[0]; } -} - -//////////////////////////////////////////////////// -// content wrangling -edges.util.escapeHtml = function(unsafe, def) { - if (def === undefined) { - def = ""; + if (thousandsSeparator !== false) { + bits[0] = bits[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator); } - if (unsafe === undefined || unsafe == null) { - return def; - } - try { - if (typeof unsafe.replace !== "function") { - return unsafe - } - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - } catch(err) { - return def; - } -} - -edges.util.toHtmlEntities = function(str) { - return str.replace(/./gm, function(s) { - // return "&#" + s.charCodeAt(0) + ";"; - return (s.match(/[a-z0-9\s]+/i)) ? s : "&#" + s.charCodeAt(0) + ";"; - }); -} - -edges.util.fromHtmlEntities = function(string) { - return (string+"").replace(/&#\d+;/gm,function(s) { - return String.fromCharCode(s.match(/\d+/gm)[0]); - }) -} - -edges.util.safeId = function(unsafe) { - return unsafe.replace(/&/g, "_") - .replace(//g, "_") - .replace(/"/g, "_") - .replace(/'/g, "_") - .replace(/\./gi,'_') - .replace(/\:/gi,'_') - .replace(/\s/gi,"_"); -} - -edges.util.numFormat = function(params) { - var reflectNonNumbers = edges.util.getParam(params, "reflectNonNumbers", false); - var prefix = edges.util.getParam(params, "prefix", ""); - var zeroPadding = edges.util.getParam(params, "zeroPadding", false); - var decimalPlaces = edges.util.getParam(params, "decimalPlaces", false); - var thousandsSeparator = edges.util.getParam(params, "thousandsSeparator", false); - var decimalSeparator = edges.util.getParam(params, "decimalSeparator", "."); - var suffix = edges.util.getParam(params, "suffix", ""); - - return function(number) { - // ensure this is really a number - var num = parseFloat(number); - if (isNaN(num)) { - if (reflectNonNumbers) { - return number; - } else { - return num; - } - } - - // first off we need to convert the number to a string, which we can do directly, or using toFixed if that - // is suitable here - if (decimalPlaces !== false) { - num = num.toFixed(decimalPlaces); - } else { - num = num.toString(); - } - - // now "num" is a string containing the formatted number that we can work on - - var bits = num.split("."); - if (zeroPadding !== false) { - var zeros = zeroPadding - bits[0].length; - var pad = ""; - for (var i = 0; i < zeros; i++) { - pad += "0"; - } - bits[0] = pad + bits[0]; - } - - if (thousandsSeparator !== false) { - bits[0] = bits[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator); - } - - if (bits.length === 1) { - return prefix + bits[0] + suffix; - } else { - return prefix + bits[0] + decimalSeparator + bits[1] + suffix; - } + if (bits.length === 1) { + return prefix + bits[0] + suffix; + } else { + return prefix + bits[0] + decimalSeparator + bits[1] + suffix; } -} + }; +}; -edges.util.numParse = function(params) { - var commaRx = new RegExp(",", "g"); +edges.util.numParse = function (params) { + var commaRx = new RegExp(",", "g"); - return function(num) { - num = num.trim(); - num = num.replace(commaRx, ""); - if (num === "") { - return 0.0; - } - return parseFloat(num); + return function (num) { + num = num.trim(); + num = num.replace(commaRx, ""); + if (num === "") { + return 0.0; } -} \ No newline at end of file + return parseFloat(num); + }; +}; From 86a129f0d7c6ff4b9ad560e1a8e10f47ba4a3c74 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Mon, 2 Dec 2024 13:52:45 +0530 Subject: [PATCH 18/21] Debug message is removed --- src/edges.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/edges.js b/src/edges.js index 2d42dec..3a9122f 100644 --- a/src/edges.js +++ b/src/edges.js @@ -476,7 +476,6 @@ edges.Edge = class { cloneQuery() { if (this.currentQuery) { - console.log("Current query", this.currentQuery); return this.currentQuery.clone(); } return false; From 0729b6631d56c13fe16afc59d051ca6e879099bf Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Mon, 2 Dec 2024 18:09:02 +0530 Subject: [PATCH 19/21] Updation to current search patch --- src/components/RefiningANDTermSelector.js | 2 +- src/datasources/solr9x.js | 19 +++++++++++ src/edges.js | 39 +++++++++++++---------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/components/RefiningANDTermSelector.js b/src/components/RefiningANDTermSelector.js index d78ba3c..c87ea63 100644 --- a/src/components/RefiningANDTermSelector.js +++ b/src/components/RefiningANDTermSelector.js @@ -313,7 +313,7 @@ edges.components.RefiningANDTermSelector = class extends edges.Component { value: term, }) ); - console.log("got term", nq); + // reset the search page to the start and then trigger the next query nq.from = 0; this.edge.pushQuery(nq); diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index 6acedb6..dafbef8 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -319,6 +319,25 @@ es.Query = class { }); return removedCount; } + + // TODO: this is a patch code update it when proper fix is added + removeQueryStrings(template) { + let removedCount = 0; + + this.queryStrings = this.queryStrings.filter((queryItem) => { + // Check if the queryString matches the template value + const matches = queryItem.queryString === template.value; + + if (matches) { + removedCount++; + } + + return !matches; + }); + + return removedCount; + } + clearMust() { this.must = []; } diff --git a/src/edges.js b/src/edges.js index 3a9122f..37f0ecc 100644 --- a/src/edges.js +++ b/src/edges.js @@ -529,7 +529,8 @@ edges.Edge = class { updateUrl() { var currentQs = window.location.search; - var qs = "?" + this.fullUrlQueryString(); + const urlString = this.fullUrlQueryString(); + var qs = urlString ? `?${urlString}` : ""; if (currentQs === qs) { return; // no need to push the state @@ -569,24 +570,28 @@ edges.Edge = class { } urlQueryArg(objectify_options) { - if (!objectify_options) { - if (this.urlQueryOptions) { - objectify_options = this.urlQueryOptions; - } else { - objectify_options = { - include_query_string: true, - include_filters: true, - include_paging: true, - include_sort: true, - include_fields: false, - include_aggregations: false, - }; + if (this.urlQuerySource) { + if (!objectify_options) { + if (this.urlQueryOptions) { + objectify_options = this.urlQueryOptions; + } else { + objectify_options = { + include_query_string: true, + include_filters: true, + include_paging: true, + include_sort: true, + include_fields: false, + include_aggregations: false, + }; + } } + var q = JSON.stringify(this.currentQuery.objectify(objectify_options)); + var obj = {}; + obj[this.urlQuerySource] = encodeURIComponent(q); + return obj; } - var q = JSON.stringify(this.currentQuery.objectify(objectify_options)); - var obj = {}; - obj[this.urlQuerySource] = encodeURIComponent(q); - return obj; + + return {}; } _makeUrlQuery(args) { From 2e65f71187f348598ab676c1b9f59497625c859e Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Mon, 16 Dec 2024 19:07:30 +0530 Subject: [PATCH 20/21] Updation to query string --- src/datasources/solr9x.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index dafbef8..e55e7b2 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -482,6 +482,8 @@ es.Query = class { include_source_filters = true, } = params; + console.log(`${include_query_string} ${include_filters}`); + const query_part = {}; const bool = {}; @@ -546,7 +548,7 @@ es.Query = class { obj._source.excludes = this.source.exclude.slice(); // Shallow copy of exclude array } } - + console.log("Returning obj", obj); return obj; } @@ -1140,9 +1142,9 @@ function _es2solr({ query }) { } if (queryPart) { - queryPart += ` ${operator} ${field}:${esQueryString}`; + queryPart += ` ${operator} ${field}:"${esQueryString}"`; } else { - queryPart = `${field}:${esQueryString}`; + queryPart = `${field}:"${esQueryString}"`; } }); From d9a0ed0a0a9e881ec5acf86f2a0de0f1a2cd4491 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Thu, 26 Dec 2024 15:41:27 +0530 Subject: [PATCH 21/21] query field is returned when cloning the edge query for solr --- src/datasources/solr9x.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datasources/solr9x.js b/src/datasources/solr9x.js index e55e7b2..0441b81 100644 --- a/src/datasources/solr9x.js +++ b/src/datasources/solr9x.js @@ -573,6 +573,7 @@ es.Query = class { exclude: [...this.source.exclude], // Shallow copy of exclude array } : null, + query: this.query ? { ...this.query } : null, partialFields: this.partialFields, scriptFields: this.scriptFields, // Add any other properties that need to be cloned