Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from metagrover/dev
Add support for geo distance query
- Loading branch information
Showing
6 changed files
with
413 additions
and
6 deletions.
There are no files selected for viewing
158 changes: 158 additions & 0 deletions
158
app/queryBlocks/singlequery/queries/geodistance.query.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import {describe, it, beforeEach, expect} from '@angular/core/testing'; | ||
import {GeoDistanceQuery} from './geodistance.query'; | ||
|
||
describe('geo_distance query format', () => { | ||
// Set initial things | ||
// set expected query format | ||
var query: GeoDistanceQuery; | ||
var expectedFormat = { | ||
'geo_distance': { | ||
'distance': '100km', | ||
'location': { | ||
'lat': '10', | ||
'lon': '10' | ||
} | ||
} | ||
}; | ||
var expectedFormatWithOption = { | ||
'geo_distance': { | ||
'distance': '100km', | ||
'location': { | ||
'lat': '10', | ||
'lon': '10' | ||
}, | ||
"distance_type": "arc", | ||
"optimize_bbox": "none", | ||
"_name": "place", | ||
"ignore_malformed": "true" | ||
} | ||
}; | ||
|
||
// instantiate query component and set the input fields | ||
beforeEach(function() { | ||
query = new GeoDistanceQuery(); | ||
query.queryName = 'geo_distance'; | ||
query.fieldName = 'location'; | ||
query.inputs = { | ||
lat: { | ||
value: '10' | ||
}, | ||
lon: { | ||
value: '10' | ||
}, | ||
distance: { | ||
value: '100km' | ||
} | ||
}; | ||
}); | ||
|
||
function isValidJson(str: string) { | ||
try { | ||
JSON.parse(str); | ||
} catch (e) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
// Test to check if queryformat is valid json | ||
it('is valid json', () => { | ||
var format = query.setFormat(); | ||
var validJson = isValidJson(JSON.stringify(format)); | ||
expect(validJson).toEqual(true); | ||
}); | ||
|
||
// Test to check if result of setformat is equal to expected query format. | ||
it('Is setformat matches with expected query format', () => { | ||
var format = query.setFormat(); | ||
expect(format).toEqual(expectedFormat); | ||
}); | ||
|
||
// Test to check if result of setformat is equal to expected query format with option. | ||
it('Is setformat matches with expected query format when pass options with query', () => { | ||
query.optionRows = [{ | ||
name: 'distance_type', | ||
value: 'arc' | ||
}, { | ||
name: 'optimize_bbox', | ||
value: 'none' | ||
}, { | ||
name: '_name', | ||
value: 'place' | ||
}, { | ||
name: 'ignore_malformed', | ||
value: 'true' | ||
}]; | ||
var format = query.setFormat(); | ||
expect(format).toEqual(expectedFormatWithOption); | ||
}); | ||
}); | ||
|
||
declare var $; | ||
describe("xhr test (geo_distance)", function () { | ||
var returnedJSON: any = {}; | ||
var status = 0; | ||
|
||
beforeEach(function (done) { | ||
var query = new GeoDistanceQuery(); | ||
query.queryName = 'geo_distance'; | ||
query.fieldName = 'place'; | ||
query.inputs = { | ||
lat: { | ||
value: '10' | ||
}, | ||
lon: { | ||
value: '10' | ||
}, | ||
distance: { | ||
value: '100km' | ||
} | ||
}; | ||
var config = { | ||
url: 'https://scalr.api.appbase.io', | ||
appname: 'mirage_test', | ||
username: 'wvCmyBy3D', | ||
password: '7a7078e0-0204-4ccf-9715-c720f24754f2' | ||
}; | ||
var url = 'https://scalr.api.appbase.io/mirage_test/geo/_search'; | ||
var query_data = query.setFormat(); | ||
var request_data = { | ||
"query": { | ||
"bool": { | ||
"must": [query_data] | ||
} | ||
} | ||
}; | ||
$.ajax({ | ||
type: 'POST', | ||
beforeSend: function(request) { | ||
request.setRequestHeader("Authorization", "Basic " + btoa(config.username + ':' + config.password)); | ||
}, | ||
url: url, | ||
contentType: 'application/json; charset=utf-8', | ||
dataType: 'json', | ||
data: JSON.stringify(request_data), | ||
xhrFields: { | ||
withCredentials: true | ||
}, | ||
success: function(res) { | ||
returnedJSON = res; | ||
status = 200; | ||
done(); | ||
}, | ||
error: function(xhr) { | ||
returnedJSON = xhr; | ||
status = xhr.status; | ||
done(); | ||
} | ||
}); | ||
}); | ||
|
||
it("Should have returned JSON and Should have atleast 1 record", function () { | ||
expect(returnedJSON).not.toEqual({}); | ||
expect(returnedJSON).not.toBeUndefined(); | ||
expect(status).toEqual(200); | ||
expect(returnedJSON.hits.hits.length).toBeGreaterThan(0); | ||
}); | ||
|
||
}); |
224 changes: 224 additions & 0 deletions
224
app/queryBlocks/singlequery/queries/geodistance.query.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
import { Component, OnInit, OnChanges, Input, Output, EventEmitter } from "@angular/core"; | ||
import { EditableComponent } from '../../editable/editable.component'; | ||
|
||
@Component({ | ||
selector: 'geo-distance-query', | ||
template: `<span class="col-xs-6 pd-0"> | ||
<div class="col-xs-6 pl-0"> | ||
<div class="form-group form-element"> | ||
<input type="text" class="form-control col-xs-12" | ||
[(ngModel)]="inputs.lat.value" | ||
placeholder="{{inputs.lat.placeholder}}" | ||
(keyup)="getFormat();" /> | ||
</div> | ||
</div> | ||
<div class="col-xs-6 pr-0"> | ||
<div class="form-group form-element"> | ||
<input type="text" class="form-control col-xs-12" | ||
[(ngModel)]="inputs.lon.value" | ||
placeholder="{{inputs.lon.placeholder}}" | ||
(keyup)="getFormat();" /> | ||
</div> | ||
</div> | ||
<div class="col-xs-6 pl-0"> | ||
<div class="form-group form-element"> | ||
<input type="text" class="form-control col-xs-12" | ||
[(ngModel)]="inputs.distance.value" | ||
placeholder="{{inputs.distance.placeholder}}" | ||
(keyup)="getFormat();" /> | ||
</div> | ||
</div> | ||
<button (click)="addOption();" class="btn btn-info btn-xs add-option"> <i class="fa fa-plus"></i> </button> | ||
</span> | ||
<div class="col-xs-12 option-container" *ngIf="optionRows.length"> | ||
<div class="col-xs-12 single-option" *ngFor="let singleOption of optionRows, let i=index"> | ||
<div class="col-xs-6 pd-l0"> | ||
<editable | ||
class = "additional-option-select-{{i}}" | ||
[editableField]="singleOption.name" | ||
[editPlaceholder]="'--choose option--'" | ||
[editableInput]="'select2'" | ||
[selectOption]="options" | ||
[passWithCallback]="i" | ||
[selector]="'additional-option-select'" | ||
[querySelector]="querySelector" | ||
[informationList]="informationList" | ||
[showInfoFlag]="true" | ||
[searchOff]="true" | ||
(callback)="selectOption($event)"> | ||
</editable> | ||
</div> | ||
<div class="col-xs-6 pd-0"> | ||
<div class="form-group form-element"> | ||
<input class="form-control col-xs-12 pd-0" type="text" [(ngModel)]="singleOption.value" placeholder="value" (keyup)="getFormat();"/> | ||
</div> | ||
</div> | ||
<button (click)="removeOption(i)" class="btn btn-grey delete-option btn-xs"> | ||
<i class="fa fa-times"></i> | ||
</button> | ||
</div> | ||
</div>`, | ||
inputs: ['appliedQuery', 'queryList', 'selectedQuery', 'selectedField','getQueryFormat', 'querySelector'], | ||
directives: [EditableComponent] | ||
}) | ||
|
||
export class GeoDistanceQuery implements OnInit, OnChanges { | ||
@Input() queryList; | ||
@Input() selectedField; | ||
@Input() appliedQuery; | ||
@Input() selectedQuery; | ||
@Output() getQueryFormat = new EventEmitter<any>(); | ||
public queryName = '*'; | ||
public fieldName = '*'; | ||
public current_query = 'geo_distance'; | ||
public information: any = { | ||
title: 'Geo Distance Query', | ||
content: `<span class="description">Filters documents that include only hits that exists within a specific distance from a geo point.</span> | ||
<a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-query.html">Read more</a>` | ||
}; | ||
public informationList: any = { | ||
'distance_type': { | ||
title: 'distance_type', | ||
content: `<span class="description">How to compute the distance. Can either be sloppy_arc (default), arc | ||
(slightly more precise but significantly slower) or plane (faster, but inaccurate on long distances and close to the poles).</span>` | ||
}, | ||
'optimize_bbox': { | ||
title: 'optimize_bbox', | ||
content: `<span class="description">Whether to use the optimization of first running a bounding box check before the distance check. | ||
Defaults to memory which will do in memory checks. Can also have values of indexed to use indexed value check | ||
(make sure the geo_point type index lat lon in this case), or none which disables bounding box optimization.</span>` | ||
}, | ||
'_name': { | ||
title: '_name', | ||
content: `<span class="description">Optional name field to identify the query</span>` | ||
}, | ||
'ignore_malformed': { | ||
title: 'ignore_malformed', | ||
content: `<span class="description">Set to true to accept geo points with invalid latitude or longitude (default is false).</span>` | ||
} | ||
}; | ||
public default_options: any = [ | ||
'distance_type', | ||
'optimize_bbox', | ||
'_name', | ||
'ignore_malformed' | ||
]; | ||
public options: any; | ||
public singleOption = { | ||
name: '', | ||
value: '' | ||
}; | ||
public optionRows: any = []; | ||
public inputs: any = { | ||
lat: { | ||
placeholder: 'Latitude', | ||
value: '*' | ||
}, | ||
lon: { | ||
placeholder: 'Longitude', | ||
value: '*' | ||
}, | ||
distance: { | ||
placeholder: 'Distance (with unit)', | ||
value: '*' | ||
} | ||
}; | ||
public queryFormat: any = {}; | ||
|
||
ngOnInit() { | ||
this.options = JSON.parse(JSON.stringify(this.default_options)); | ||
try { | ||
if(this.appliedQuery[this.current_query][this.fieldName]['lat']) { | ||
this.inputs.lat.value = this.appliedQuery[this.current_query][this.fieldName]['lat']; | ||
} | ||
if(this.appliedQuery[this.current_query][this.fieldName]['lon']) { | ||
this.inputs.lon.value = this.appliedQuery[this.current_query][this.fieldName]['lon']; | ||
} | ||
if(this.appliedQuery[this.current_query][this.fieldName]['distance']) { | ||
this.inputs.distance.value = this.appliedQuery[this.current_query][this.fieldName]['distance']; | ||
} | ||
for (let option in this.appliedQuery[this.current_query][this.fieldName]) { | ||
if (option != 'lat' && option != 'lon' && option != 'distance') { | ||
var obj = { | ||
name: option, | ||
value: this.appliedQuery[this.current_query][this.fieldName][option] | ||
}; | ||
this.optionRows.push(obj); | ||
} | ||
} | ||
} catch(e) {} | ||
this.filterOptions(); | ||
this.getFormat(); | ||
} | ||
|
||
ngOnChanges() { | ||
if(this.selectedField != '') { | ||
if(this.selectedField !== this.fieldName) { | ||
this.fieldName = this.selectedField; | ||
this.getFormat(); | ||
} | ||
} | ||
if(this.selectedQuery != '') { | ||
if(this.selectedQuery !== this.queryName) { | ||
this.queryName = this.selectedQuery; | ||
this.optionRows = []; | ||
this.getFormat(); | ||
} | ||
} | ||
} | ||
|
||
getFormat() { | ||
if (this.queryName === 'geo_distance') { | ||
this.queryFormat = this.setFormat(); | ||
this.getQueryFormat.emit(this.queryFormat); | ||
} | ||
} | ||
|
||
setFormat() { | ||
var queryFormat = {}; | ||
queryFormat[this.queryName] = { | ||
distance: this.inputs.distance.value, | ||
}; | ||
queryFormat[this.queryName][this.fieldName] = { | ||
lat: this.inputs.lat.value, | ||
lon: this.inputs.lon.value | ||
}; | ||
this.optionRows.forEach(function(singleRow: any) { | ||
queryFormat[this.queryName][singleRow.name] = singleRow.value; | ||
}.bind(this)); | ||
return queryFormat; | ||
} | ||
|
||
selectOption(input: any) { | ||
input.selector.parents('.editable-pack').removeClass('on'); | ||
this.optionRows[input.external].name = input.val; | ||
this.filterOptions(); | ||
setTimeout(function() { | ||
this.getFormat(); | ||
}.bind(this), 300); | ||
} | ||
|
||
filterOptions() { | ||
this.options = this.default_options.filter(function(opt) { | ||
var flag = true; | ||
this.optionRows.forEach(function(row) { | ||
if(row.name === opt) { | ||
flag = false; | ||
} | ||
}); | ||
return flag; | ||
}.bind(this)); | ||
} | ||
|
||
addOption() { | ||
var singleOption = JSON.parse(JSON.stringify(this.singleOption)); | ||
this.filterOptions(); | ||
this.optionRows.push(singleOption); | ||
} | ||
|
||
removeOption(index: Number) { | ||
this.optionRows.splice(index, 1); | ||
this.filterOptions(); | ||
this.getFormat(); | ||
} | ||
} |
Oops, something went wrong.