Skip to content

Commit

Permalink
Merge pull request #22 from metagrover/dev
Browse files Browse the repository at this point in the history
Add support for geo distance query
  • Loading branch information
farhan687 committed Oct 13, 2016
2 parents 3e7c55d + c6a74a8 commit f10cae3
Show file tree
Hide file tree
Showing 6 changed files with 413 additions and 6 deletions.
158 changes: 158 additions & 0 deletions app/queryBlocks/singlequery/queries/geodistance.query.spec.ts
@@ -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 app/queryBlocks/singlequery/queries/geodistance.query.ts
@@ -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();
}
}

0 comments on commit f10cae3

Please sign in to comment.