How to build up an elasticsearch query in a better way? - javascript

I am currently using the elasticsearch helper to run queries on my elasticsearch database. Currently I am using vue.js to help build this application.
When I am paginating my results I use this query:
this.client.search({
index: 'node',
type: 'vakantie',
from: 12 * index,
size: this.size,
}).then(function (resp) {
this.travels = resp.hits.hits;
this.currentPage = index;
}.bind(this), function (err) {
console.trace(err.message);
});
I also have an input box above my results that a user can type a search term into and it will instantly filter down results using this query:
index: 'node',
type: 'vakantie',
from: 0,
size: this.size,
body: {
query: {
match_phrase_prefix: {
title: {
query: this.query,
slop: 10,
max_expansions: 50
}
}
},
highlight: {
fields: {
title: {}
},
pre_tags: ["<span class='highlight'>"],
post_tags: ["</span>"]
}
}
I have filters and sort methods in place as well, and I know how to use a query with elasticsearch to combine multiple search queries.
Obviously I don't want to write a search query for every combination of filter + input + sort possible. Is there a better way to build up my search queries than using a javascript object like this?
Hopefully I am getting my point across, I have a bit too much custom code to simply paste it here.

Related

Custom parameters for bootstrap-table server side pagination

I have a service created with spring boot, for which I am trying to display its data using the bootstrap-table library.
My service allows pagination with the query parameters ?page=x&size=y, where page starts at 0.
The response for the query returns something that looks like this:
{
"_embedded": {
"catalogueOrders": [ .... ]
},
"page": {
"size": 20,
"totalElements": 11,
"totalPages": 1,
"number": 0
}
}
Where _embedded.catalogueOrders contains all the data, and page contains the totals.
I tried configuring my table as following:
$('#orderTable').bootstrapTable({
url: "http://localhost:8088/catalogueOrders?orderStatus=" + orderState,
columns: [
{
field: 'orderId',
title: 'Order ID'
},
{
field: 'priority',
title: 'Priority'
}
],
pagination: true,
sidePagination: 'server',
totalField: 'page.totalElements',
pageSize: 5,
pageList: [5, 10, 25],
responseHandler: function(res) {
console.log(res)
return res["_embedded"]["catalogueOrders"]
}
})
This is able to retrieve and display the data, however it returns all the results, clearly due to it not knowing how to apply the pagination. Total elements doesn't seem to be retrieved either, as the table displays Showing 1 to 5 of undefined rows. Also, if I replace the responseHandler with dataField: '_embedded.catalogueOrders', it's no longer displaying the data.
How do I configure the query parameters needed for pagination?
And am I doing anything wrong when I try and configure dataField and totalField?
Figured it out:
Not sure what was wrong with the dataField and totalField, but it seems to not work with nested fields. To resolve this, I formatted the response into a new object inside responseHandler:
dataField: 'data',
totalField: 'total',
responseHandler: function(res) {
return {
data: res["_embedded"]["catalogueOrders"],
total: res["page"]["totalElements"]
}
}
As for the query parameters, by default, bootstrap-table provides the parameters limit and offset. To customize that and convert to size and page like in my case, the queryParams function can be provided:
queryParams: function(p) {
return {
page: Math.floor(p.offset / p.limit),
size: p.limit
}
}
one, yes, it doesn’t work with nested fields. if you want to use nested fields, try on sass code (get the compiler, just search up on web, there’s plenty of posts on the web).
two, i’m not exactly sure what you’re talking about, but you can set up a css variable
:root{
/*assign variables*/
—-color-1: red;
—-color-2: blue;
}
/*apply variables
p {
color: var(-—color-1):
}
you can find loads of info on this on the web.

Select2 - Pass back additional data via ajax call

Ok, I feel like I'm going crazy here. I'm using the select2 jquery plugin (version 4), and retrieving data via ajax. So you can type in a name, and it will return that contact information. But I also want to return what organization that contact is a part of.
Here is my select2 initialization:
$('#contact_id').select2({
ajax: {
url: 'example.com/contacts/select',
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term,
page: params.page
};
},
processResults: function (data) {
return {
results: data
};
},
cache: true
},
minimumInputLength: 3,
maximumSelectionLength: 1
});
And here is the data I'm returning (laravel framework):
foreach($contacts as $con) {
$results[] = [
'id' => $con->contact_id,
'text' => $con->full_name,
'org' => [
'org_id' => $con->organization_id,
'org_name' => $con->org_name
]
];
}
return response()->json($results);
So isn't 'org' supposed to be attached to either the created option or select element by select2? So I could do something like $('#contact_id').select2().find(':selected').data('data').org or $('#contact_id').select2().data('data').org or something like that?
Idealistically, this would look like:
<select>
<option value="43" data-org="{org_id:377, org_name:'Galactic Empire'}">Darth Vader</option>
</select>
I swear I confirmed this worked last week, but now it's completely ignoring that org property. I have confirmed that the json data being returned does include org with the proper org_id and org_name. I haven't been able to dig anything up online, only this snippet of documentation:
The id and text properties are required on each object, and these are the properties that Select2 uses for the internal data objects. Any additional paramters passed in with data objects will be included on the data objects that Select2 exposes.
So can anyone help me with this? I've already wasted a couple hours on this.
EDIT: Since I haven't gotten any responses, my current plan is to use the processResults callback to spawn hidden input fields or JSON blocks that I will reference later in my code. I feel like this is a hacky solution given the situation, but if there's no other way, that's what I'll do. I'd rather that than do another ajax call to get the organization. When I get around to implementing it, I'll post my solution.
Can't comment for now (low reputation).. so... answering to slick:
Including additional data (v4.0):
processResults: function (data) {
data = data.map(function (item) {
return {
id: item.id_field,
text: item.text_field,
otherfield: item.otherfield
};
});
return { results: data };
}
Reading the data:
var data=$('#contact_id').select2('data')[0];
console.log(data.otherfield);
Can't remember what I was doing wrong, but with processResults(data), data contains the full response. In my implementation below, I access this info when an item is selected:
$('#select2-box').select2({
placeholder: 'Search Existing Contacts',
ajax: {
url: '/contacts/typeahead',
dataType: 'json',
delay: 250,
data: function(params){
return {
q: params.term,
type: '',
suggestions: 1
};
},
processResults: function(data, params){
//Send the data back
return {
results: data
};
}
},
minimumInputLength: 2
}).on('select2:select', function(event) {
// This is how I got ahold of the data
var contact = event.params.data;
// contact.suggestions ...
// contact.organization_id ...
});
// Data I was returning
[
{
"id":36167, // ID USED IN SELECT2
"avatar":null,
"organization_id":28037,
"text":"John Cena - WWE", // TEXT SHOWN IN SELECT2
"suggestions":[
{
"id":28037,
"text":"WWE",
"avatar":null
},
{
"id":21509,
"text":"Kurt Angle",
"avatar":null
},
{
"id":126,
"text":"Mark Calaway",
"avatar":null
},
{
"id":129,
"text":"Ricky Steamboat",
"avatar":null
},
{
"id":131,
"text":"Brock Lesnar",
"avatar":null
}
]
}
]

ElasticSearch Javascript, Highlight not working

we switched recently to ElasticSearch Angular version and everything is working as expected except the Highlight, which is not returned at all.
This is how I setup a demo query:
$esClient.search({
index: 'myIndex',
body: {
size: 10,
from: 0,
query: query,
highlight: {
fields: {
"_all": { "pre_tags": ["<em>"], "post_tags": ["</em>"] }
}
}
}
}).then(function (result) {
// map the resultset for Row Template
var currentRows = result.hits.hits.map(function (record) {
return {
"type": record._type,
"entity": record._source, // the result
"highlight": record.highlight, // the highlights
"id": record._id // Search record ID
};
});
});
If I use the same code with a classic XmlHttpRequest and pass the query model inlcuding the highlight, I get back a JSON which contains an highlight array per each result, while using the ElasticSearch Angular client the query succeed but I don't get back the highlight.
Am I doing something wrong?
I think you might want to change to this format:
{
"query" : {...},
"highlight" : {
"pre_tags" : ["<tag1>"],
"post_tags" : ["</tag1>"],
"fields" : {
"_all" : {}
}
}}
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-highlighting.html

How bind search values in mongodb with mongoose

I have the following code in my /search/:query route:
var param = {
query: req.query['query']
}
MyModel.find({
"$or": [
{ 'name': req.param.query },
{ 'age': req.param.query },
{ 'event': req.param.query },
]
}, function (err, results) {
if (err) {
console.log(err)
}
else {
res.render('index', {
data: results
});
}
}
);
And is good, i can search for pretty much every data that i want, but only individually. What if i want search name + age, can i? Example: 'Leo 22'.
There is any way that mongoose help me with this?
UPDATE:
My problem is:
I have tables lists it titles, this title is the concatenation of 'eventName' and 'eventDate'.
Real examples of this fields:
'Special Event - 20/12/2015'
'Classic Event - 12/03/2015'
'Hot Summer Event - 05/07/2005'
Every week will be create 4 events. In some point, a user will search for an old event, and i believe that the user will search in this format:'EVENT NAME - EVENT DATE'..
So i need a way to bind this values in my controllers.
I'm no familiar with mongoose but in order to do that, you must have a way to bind your query param to the attribute you want to search. Otherwise, they wouldn't know Leo is name and 22 is age.
Ur path would be like search?name=:name&age=:age&event=:event and in your code, you will have to process like if the param is not null, add and condition to it.
It seems you are using only one parameter (req.param.query) to filter all attributes. That's not mongoose related: you could create distinct parameters for each attribute and pass them along the query string.
For instance:
"$or": [
{ 'name': req.param.name },
{ 'age': req.param.age },
{ 'event': req.param.event },
]
And your HTTP request will be like this:
http://youraddress/expressRoute?name=Leo&age=22

Add multiple records to model's collection Sailsjs

I have the following models in my Sailsjs application with a many-to-many relationship:
event.js:
attributes: {
title : { type: 'string', required: true },
description : { type: 'string', required: true },
location : { type: 'string', required: true },
maxMembers : { type: 'integer', required: true },
currentMembers : { collection: 'user', via: 'eventsAttending', dominant: true },
creator : { model: 'user', required: true },
invitations : { collection: 'invitation', via: 'eventID' },
tags : { collection: 'tag', via: 'taggedEvents', dominant: true },
lat : { type: 'float' },
lon : { type: 'float' },
},
tags.js:
attributes: {
tagName : { type: 'string', unique: true, required: true },
taggedEvents : { collection: 'event', via: 'tags' },
},
Based on the documentation, this relationship looks correct. I have the following method in tag.js that accepts an array of tag strings, and an event id, and is supposed to add or remove the tags that were passed in:
modifyTags: function (tags, eventId) {
var tagRecords = [];
_.forEach(tags, function(tag) {
Tag.findOrCreate({tagName: tag}, {tagName: tag}, function (error, result) {
tagRecords.push({id: result.id})
})
})
Event.findOneById(eventId).populate('tags').exec(function(error, event){
console.log(event)
var currentTags = event.tags;
console.log(currentTags)
delete currentTags.add;
delete currentTags.remove;
if (currentTags.length > 0) {
currentTags = _.pluck(currentTags, 'id');
}
var modifiedTags = _.pluck(tagRecords, 'id');
var tagsToAdd = _.difference(modifiedTags, currentTags);
var tagsToRemove = _.difference(currentTags, modifiedTags);
console.log('current', currentTags)
console.log('remove', tagsToRemove)
console.log('add', tagsToAdd)
if (tagsToAdd.length > 0) {
_.forEach(tagsToAdd, function (tag) {
event.tags.add(tag);
})
event.save(console.log)
}
if (tagsToRemove.length > 0) {
_.forEach(tagsToRemove, function (tagId) {
event.tags.remove(tagId)
})
event.save()
}
})
}
This is how the method is called from the event model:
afterCreate: function(record, next) {
Tag.modifyTags(tags, record.id)
next();
}
When I post to event/create, I get this result: http://pastebin.com/PMiqBbfR.
It looks as if the method call itself is looped over, rather than just the tagsToAdd or tagsToRemove array. Whats more confusing is that at the end, in the last log of the event, it looks like the event has the correct tags. When I then post to event/1, however, the tags array is empty. I've also tried saving immediately after each .add(), but still get similar results.
Ideally, I'd like to loop over both the tagsToAdd and tagsToRemove arrays, modify their ids in the model's collection, and then call .save() once on the model.
I have spent a ton of time trying to debug this, so any help would be greatly appreciated!
There are a few problems with your implementation, but the main issue is that you're treating certain methods--namely .save() and .findOrCreate as synchronous methods, when they are (like all Waterline methods) asynchronous, requiring a callback. So you're effectively running a bunch of code in parallel and not waiting for it to finish before returning.
Also, since it seems like what you're trying to do is replace the current event tags with this new list, the method you came up with is a bit over-engineered--you don't need to use event.tags.add and event.tags.remove. You can just use plain old update.
So you could probably rewrite the modifyTags method as:
modifyTags: function (tags, eventId, mainCb) {
// Asynchronously transform the `tags` array into an array of Tag records
async.map(tags, function(tag, cb) {
// For each tag, find or create a new record.
// Since the async.map `cb` argument expects a function with
// the standard (error, result) node signature, this will add
// the new (or existing) Tag instance to the resulting array.
// If an error occurs, async.map will exit early and call the
// "done()" function below
Tag.findOrCreate({tagName: tag}, {tagName: tag}, cb);
}, function done (err, tagRecords) {
if (err) {return mainCb(err);}
// Update the event with the new tags
Event.update({id: eventId}, {tags: tagRecords}).exec(mainCb);
});
}
See the full docs for async.map here.
If you wanted to stick with your implementation using .add and .remove, you would still want to use async.map, and do the rest of your logic in the done method. You don't need two .save calls; just do run all the .add and .remove code first, then do a single .save(mainCb) to finish it off.
And I don't know what you're trying to accomplish by deleting the .add and .remove methods from currentTags (which is a direct reference to event.tags), but it won't work and will just cause confusion later!

Categories

Resources