diff --git a/pdb-plotting/.gitignore b/pdb-plotting/.gitignore index 267b0fd..50bb473 100644 --- a/pdb-plotting/.gitignore +++ b/pdb-plotting/.gitignore @@ -3,3 +3,4 @@ /.settings/ /.classpath /.project +/build/ diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java b/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java index 6507a6b..8d60b76 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java @@ -2,9 +2,17 @@ package org.lucares.pdbui; import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.lucares.ludb.Proposal; +import org.lucares.pdbui.domain.AutocompleteProposal; +import org.lucares.pdbui.domain.AutocompleteProposalByValue; +import org.lucares.pdbui.domain.AutocompleteResponse; import org.lucares.pdbui.domain.PlotRequest; import org.lucares.pdbui.domain.PlotResponse; +import org.lucares.performance.db.CollectionUtils; import org.lucares.recommind.logs.InternalPlottingException; import org.lucares.recommind.logs.Plotter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -13,15 +21,18 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller @EnableAutoConfiguration -public class PdbController implements HardcodedValues { +public class PdbController implements HardcodedValues, CollectionUtils { private final Plotter plotter; + private final PdbRepository db; - public PdbController(final Plotter plotter) { + public PdbController(final PdbRepository db, final Plotter plotter) { + this.db = db; this.plotter = plotter; } @@ -34,8 +45,12 @@ public class PdbController implements HardcodedValues { PlotResponse createPlot(@RequestBody final PlotRequest request) { try { - System.out.println(request.getQuery()); - final File image = plotter.plot(request.getQuery(), request.getHeight(), request.getWidth()); + final String query = request.getQuery(); + final int height = request.getHeight(); + final int width = request.getWidth(); + + System.out.println(query); + final File image = plotter.plot(query, height, width); final Path relativeImagePath = plotter.getOutputDir().relativize(image.toPath()); return new PlotResponse(WEB_IMAGE_OUTPUT_PATH + "/" + relativeImagePath.toString()); @@ -45,4 +60,47 @@ public class PdbController implements HardcodedValues { } } + @RequestMapping(path = "/autocomplete", // + method = RequestMethod.GET, // + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, // + produces = MediaType.APPLICATION_JSON_UTF8_VALUE // + ) + @ResponseBody + AutocompleteResponse autocomplete(@RequestParam(name = "query") final String query, + @RequestParam(name = "caretIndex") final int caretIndex) { + + try { + final AutocompleteResponse result = new AutocompleteResponse(); + final int zeroBasedCaretIndex = caretIndex - 1; + + final List proposals = db.autocomplete(query, zeroBasedCaretIndex); + final List nonEmptyProposals = filter(proposals, p -> p.getResults() > 0); + + final List autocompleteProposals = toAutocompleteProposals(nonEmptyProposals); + Collections.sort(autocompleteProposals, new AutocompleteProposalByValue()); + + result.setProposals(autocompleteProposals); + return result; + + } catch (final Exception e) { + e.printStackTrace(); + throw new InternalServerError(e); + } + } + + private List toAutocompleteProposals(final List proposals) { + + final List result = new ArrayList<>(); + + for (final Proposal proposal : proposals) { + final AutocompleteProposal e = new AutocompleteProposal(); + e.setValue(proposal.getProposedTag()); + e.setProposedQuery(proposal.getProposedQuery()); + + result.add(e); + } + + return result; + } + } diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/PdbRepository.java b/pdb-ui/src/main/java/org/lucares/pdbui/PdbRepository.java index dc83505..bc50194 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/PdbRepository.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/PdbRepository.java @@ -2,7 +2,9 @@ package org.lucares.pdbui; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; +import org.lucares.ludb.Proposal; import org.lucares.performance.db.PerformanceDb; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Value; @@ -21,6 +23,10 @@ public class PdbRepository implements DisposableBean { return db; } + public List autocomplete(final String query, final int caretIndex) { + return db.autocomplete(query, caretIndex); + } + @Override public void destroy() { db.close(); diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposal.java b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposal.java new file mode 100644 index 0000000..899e97f --- /dev/null +++ b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposal.java @@ -0,0 +1,27 @@ +package org.lucares.pdbui.domain; + +public class AutocompleteProposal { + private String value; + private String proposedQuery; + + public String getValue() { + return value; + } + + public void setValue(final String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + public void setProposedQuery(final String proposedQuery) { + this.proposedQuery = proposedQuery; + } + + public String getProposedQuery() { + return proposedQuery; + } +} diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposalByValue.java b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposalByValue.java new file mode 100644 index 0000000..cd3d4f1 --- /dev/null +++ b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposalByValue.java @@ -0,0 +1,12 @@ +package org.lucares.pdbui.domain; + +import java.util.Comparator; + +public class AutocompleteProposalByValue implements Comparator { + + @Override + public int compare(final AutocompleteProposal o1, final AutocompleteProposal o2) { + return o1.getValue().compareToIgnoreCase(o2.getValue()); + } + +} diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteResponse.java b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteResponse.java new file mode 100644 index 0000000..a55b66a --- /dev/null +++ b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteResponse.java @@ -0,0 +1,21 @@ +package org.lucares.pdbui.domain; + +import java.util.List; + +public class AutocompleteResponse { + private List proposals; + + public List getProposals() { + return proposals; + } + + public void setProposals(final List proposals) { + this.proposals = proposals; + } + + @Override + public String toString() { + return String.valueOf(proposals); + } + +} diff --git a/pdb-ui/src/main/resources/resources/css/autocomplete.min.css b/pdb-ui/src/main/resources/resources/css/autocomplete.min.css new file mode 100644 index 0000000..03b35a9 --- /dev/null +++ b/pdb-ui/src/main/resources/resources/css/autocomplete.min.css @@ -0,0 +1 @@ +.autocomplete,.autocomplete.open{transition:all .5s ease 0s;overflow-y:hidden}.autocomplete{position:absolute;max-height:0;transition-duration:.3s;transition-property:all;transition-timing-function:cubic-bezier(0,1,.5,1)}.autocomplete.open{display:block;background-color:#FAFAFA;max-height:500px;transition-duration:.3s;transition-property:all;transition-timing-function:cubic-bezier(0,1,.5,1)}.autocomplete.open:empty,.autocomplete:empty{display:none}.autocomplete:active,.autocomplete:focus,.autocomplete:hover{background-color:#FAFAFA;transition:all .5s ease 0s}.autocomplete>ul{list-style-type:none;margin:0;padding:0}.autocomplete>ul>li{cursor:pointer;padding:5px 0 5px 10px}.autocomplete>ul>li.locked{cursor:inherit}.autocomplete>ul>li.active,.autocomplete>ul>li:active,.autocomplete>ul>li:focus,.autocomplete>ul>li:hover{background-color:#EEE;transition:all .5s ease 0s}.autocomplete>ul>li.active a:active,.autocomplete>ul>li.active a:focus,.autocomplete>ul>li.active a:hover,.autocomplete>ul>li:active a:active,.autocomplete>ul>li:active a:focus,.autocomplete>ul>li:active a:hover,.autocomplete>ul>li:focus a:active,.autocomplete>ul>li:focus a:focus,.autocomplete>ul>li:focus a:hover,.autocomplete>ul>li:hover a:active,.autocomplete>ul>li:hover a:focus,.autocomplete>ul>li:hover a:hover{text-decoration:none}input[data-autocomplete]{border-color:grey;border-style:none none solid;border-width:0 0 1px;margin:0;padding:5px;width:100%} \ No newline at end of file diff --git a/pdb-ui/src/main/resources/resources/css/design.css b/pdb-ui/src/main/resources/resources/css/design.css index 2b965f4..1823412 100644 --- a/pdb-ui/src/main/resources/resources/css/design.css +++ b/pdb-ui/src/main/resources/resources/css/design.css @@ -49,6 +49,17 @@ html, body { box-sizing: border-box; } +#search-input-wrapper { + text-align: left; + display: flex; + padding: 5px; +} + +#search-input-wrapper .autocomplete { + overflow-y: scroll; +} + + #search-bar #search-submit { margin-right:3px; } diff --git a/pdb-ui/src/main/resources/resources/index.html b/pdb-ui/src/main/resources/resources/index.html index 60a8951..41aaddd 100644 --- a/pdb-ui/src/main/resources/resources/index.html +++ b/pdb-ui/src/main/resources/resources/index.html @@ -3,10 +3,12 @@ + +
@@ -14,7 +16,10 @@
diff --git a/pdb-ui/src/main/resources/resources/js/.gitignore b/pdb-ui/src/main/resources/resources/js/.gitignore new file mode 100644 index 0000000..17796fe --- /dev/null +++ b/pdb-ui/src/main/resources/resources/js/.gitignore @@ -0,0 +1,5 @@ +/horsey-master/ +/horsey-master.zip +/autocomplete.js-2.1.0/ +/autocomplete.js-2.1.0.zip +/search.js diff --git a/pdb-ui/src/main/resources/resources/js/autocomplete.js b/pdb-ui/src/main/resources/resources/js/autocomplete.js new file mode 100644 index 0000000..3614c68 --- /dev/null +++ b/pdb-ui/src/main/resources/resources/js/autocomplete.js @@ -0,0 +1,449 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AutoComplete = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o @baptistedonaux + */ +var AutoComplete = (function () { + // Constructor + function AutoComplete(params, selector) { + if (params === void 0) { params = {}; } + if (selector === void 0) { selector = "[data-autocomplete]"; } + if (Array.isArray(selector)) { + selector.forEach(function (s) { + new AutoComplete(params, s); + }); + } + else if (typeof selector == "string") { + var elements = document.querySelectorAll(selector); + Array.prototype.forEach.call(elements, function (input) { + new AutoComplete(params, input); + }); + } + else { + AutoComplete.prototype.create(AutoComplete.merge(AutoComplete.defaults, params, { + DOMResults: document.createElement("div") + }), selector); + } + } + AutoComplete.prototype.create = function (params, element) { + params.Input = element; + if (params.Input.nodeName.match(/^INPUT$/i) && (params.Input.hasAttribute("type") === false || params.Input.getAttribute("type").match(/^TEXT|SEARCH$/i))) { + params.Input.setAttribute("autocomplete", "off"); + params._Position(params); + params.Input.parentNode.appendChild(params.DOMResults); + params.$Listeners = { + blur: params._Blur.bind(params), + destroy: AutoComplete.prototype.destroy.bind(null, params), + focus: params._Focus.bind(params), + keyup: AutoComplete.prototype.event.bind(null, params), + keydown: function (event) { + if (event.keyCode == 38 || event.keyCode == 40) { + event.preventDefault(); + } + }, + position: params._Position.bind(params) + }; + for (var event in params.$Listeners) { + params.Input.addEventListener(event, params.$Listeners[event]); + } + } + }; + AutoComplete.prototype.event = function (params, event) { + var eventIdentifier = function (condition) { + if ((match === true && mapping.Operator == ConditionOperator.AND) || (match === false && ConditionOperator.OR)) { + condition = AutoComplete.merge({ + Not: false + }, condition); + if (condition.hasOwnProperty("Is")) { + if (condition.Is == event.keyCode) { + match = !condition.Not; + } + else { + match = condition.Not; + } + } + else if (condition.hasOwnProperty("From") && condition.hasOwnProperty("To")) { + if (event.keyCode >= condition.From && event.keyCode <= condition.To) { + match = !condition.Not; + } + else { + match = condition.Not; + } + } + } + }; + for (var name in params.KeyboardMappings) { + var mapping = AutoComplete.merge({ + Operator: ConditionOperator.AND + }, params.KeyboardMappings[name]), match = ConditionOperator.AND == mapping.Operator; + mapping.Conditions.forEach(eventIdentifier); + if (match === true) { + mapping.Callback.bind(params, event)(); + } + } + }; + AutoComplete.prototype.ajax = function (params, callback, timeout) { + if (timeout === void 0) { timeout = true; } + if (params.$AjaxTimer) { + window.clearTimeout(params.$AjaxTimer); + } + if (timeout === true) { + params.$AjaxTimer = window.setTimeout(AutoComplete.prototype.ajax.bind(null, params, callback, false), params.Delay); + } + else { + if (params.Request) { + params.Request.abort(); + } + var propertyHttpHeaders = Object.getOwnPropertyNames(params.HttpHeaders), method = params._HttpMethod(), url = params._Url(), queryParams = params._QueryArg() + "=" + params._Pre(); + if (method.match(/^GET$/i)) { + url += "?" + queryParams; + } + params.Request = new XMLHttpRequest(); + params.Request.open(method, url, true); + for (var i = propertyHttpHeaders.length - 1; i >= 0; i--) { + params.Request.setRequestHeader(propertyHttpHeaders[i], params.HttpHeaders[propertyHttpHeaders[i]]); + } + params.Request.onreadystatechange = callback; + params.Request.send(queryParams); + } + }; + AutoComplete.prototype.destroy = function (params) { + for (var event in params.$Listeners) { + params.Input.removeEventListener(event, params.$Listeners[event]); + } + params.DOMResults.parentNode.removeChild(params.DOMResults); + }; + AutoComplete.merge = function () { + var merge = {}, tmp; + for (var i = 0; i < arguments.length; i++) { + for (tmp in arguments[i]) { + merge[tmp] = arguments[i][tmp]; + } + } + return merge; + }; + AutoComplete.defaults = { + Delay: 150, + EmptyMessage: "No result here", + Highlight: { + getRegex: function (value) { + return new RegExp(value, "ig"); + }, + transform: function (value) { + return "" + value + ""; + } + }, + HttpHeaders: { + "Content-type": "application/x-www-form-urlencoded" + }, + Limit: 0, + HttpMethod: "GET", + QueryArg: "q", + Url: null, + KeyboardMappings: { + "Enter": { + Conditions: [{ + Is: 13, + Not: false + }], + Callback: function () { + if (this.DOMResults.getAttribute("class").indexOf("open") != -1) { + var liActive = this.DOMResults.querySelector("li.active"); + if (liActive !== null) { + this._Select(liActive); + this.DOMResults.setAttribute("class", "autocomplete"); + } + } + }, + Operator: ConditionOperator.AND + }, + "KeyUpAndDown": { + Conditions: [{ + Is: 38, + Not: false + }, + { + Is: 40, + Not: false + }], + Callback: function (event) { + event.preventDefault(); + var first = this.DOMResults.querySelector("li:first-child:not(.locked)"), last = this.DOMResults.querySelector("li:last-child:not(.locked)"), active = this.DOMResults.querySelector("li.active"); + if (active) { + var currentIndex = Array.prototype.indexOf.call(active.parentNode.children, active), position = currentIndex + (event.keyCode - 39), lisCount = this.DOMResults.getElementsByTagName("li").length; + if (position < 0) { + position = lisCount - 1; + } + else if (position >= lisCount) { + position = 0; + } + active.setAttribute("class", ""); + active.parentElement.childNodes.item(position).setAttribute("class", "active"); + } + else if (last && event.keyCode == 38) { + last.setAttribute("class", "active"); + } + else if (first) { + first.setAttribute("class", "active"); + } + }, + Operator: ConditionOperator.OR + }, + "AlphaNum": { + Conditions: [{ + Is: 13, + Not: true + }, { + From: 35, + To: 40, + Not: true + }], + Callback: function () { + var oldValue = this.Input.getAttribute("data-autocomplete-old-value"), currentValue = this._Pre(); + if (currentValue !== "") { + if (!oldValue || currentValue != oldValue) { + this.DOMResults.setAttribute("class", "autocomplete open"); + } + AutoComplete.prototype.ajax(this, function () { + if (this.Request.readyState == 4 && this.Request.status == 200) { + this._Render(this._Post(this.Request.response)); + this._Open(); + } + }.bind(this)); + } + }, + Operator: ConditionOperator.AND + } + }, + DOMResults: null, + Request: null, + Input: null, + /** + * Return the message when no result returns + */ + _EmptyMessage: function () { + var emptyMessage = ""; + if (this.Input.hasAttribute("data-autocomplete-empty-message")) { + emptyMessage = this.Input.getAttribute("data-autocomplete-empty-message"); + } + else if (this.EmptyMessage !== false) { + emptyMessage = this.EmptyMessage; + } + else { + emptyMessage = ""; + } + return emptyMessage; + }, + /** + * Returns the maximum number of results + */ + _Limit: function () { + var limit = this.Input.getAttribute("data-autocomplete-limit"); + if (isNaN(limit)) { + return this.Limit; + } + return parseInt(limit); + }, + /** + * Apply transformation on labels response + */ + _Highlight: function (label) { + return label.replace(this.Highlight.getRegex(this._Pre()), this.Highlight.transform); + }, + /** + * Returns the HHTP method to use + */ + _HttpMethod: function () { + if (this.Input.hasAttribute("data-autocomplete-method")) { + return this.Input.getAttribute("data-autocomplete-method"); + } + return this.HttpMethod; + }, + /** + * Returns the query param to use + */ + _QueryArg: function () { + if (this.Input.hasAttribute("data-autocomplete-param-name")) { + return this.Input.getAttribute("data-autocomplete-param-name"); + } + return this.QueryArg; + }, + /** + * Returns the URL to use for AJAX request + */ + _Url: function () { + if (this.Input.hasAttribute("data-autocomplete")) { + return this.Input.getAttribute("data-autocomplete"); + } + return this.Url; + }, + /** + * Manage the close + */ + _Blur: function (now) { + if (now === void 0) { now = false; } + if (now) { + this.DOMResults.setAttribute("class", "autocomplete"); + } + else { + var params = this; + setTimeout(function () { + params._Blur(true); + }, 150); + } + }, + /** + * Manage the open + */ + _Focus: function () { + var oldValue = this.Input.getAttribute("data-autocomplete-old-value"); + if (!oldValue || this.Input.value != oldValue) { + this.DOMResults.setAttribute("class", "autocomplete open"); + } + }, + /** + * Bind all results item if one result is opened + */ + _Open: function () { + var params = this; + Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function (li) { + if (li.getAttribute("class") != "locked") { + li.onclick = function () { + params._Select(li); + }; + } + }); + }, + /** + * Position the results HTML element + */ + _Position: function () { + this.DOMResults.setAttribute("class", "autocomplete"); + this.DOMResults.setAttribute("style", "top:" + (this.Input.offsetTop + this.Input.offsetHeight) + "px;left:" + this.Input.offsetLeft + "px;width:" + this.Input.offsetWidth + "px;"); + }, + /** + * Execute the render of results DOM element + */ + _Render: function (response) { + var ul; + if (typeof response == "string") { + ul = this._RenderRaw(response); + } + else { + ul = this._RenderResponseItems(response); + } + if (this.DOMResults.hasChildNodes()) { + this.DOMResults.removeChild(this.DOMResults.childNodes[0]); + } + this.DOMResults.appendChild(ul); + }, + /** + * ResponseItems[] rendering + */ + _RenderResponseItems: function (response) { + var ul = document.createElement("ul"), li = document.createElement("li"); + // Order + if (this._Limit() < 0) { + response = response.reverse(); + } + for (var item = 0; item < response.length; item++) { + li.innerHTML = response[item].Label; + li.setAttribute("data-autocomplete-value", response[item].Value); + ul.appendChild(li); + li = document.createElement("li"); + } + return ul; + }, + /** + * string response rendering (RAW HTML) + */ + _RenderRaw: function (response) { + var ul = document.createElement("ul"), li = document.createElement("li"); + if (response.length > 0) { + this.DOMResults.innerHTML = response; + } + else { + var emptyMessage = this._EmptyMessage(); + if (emptyMessage !== "") { + li.innerHTML = emptyMessage; + li.setAttribute("class", "locked"); + ul.appendChild(li); + } + } + return ul; + }, + /** + * Deal with request response + */ + _Post: function (response) { + try { + var returnResponse = []; + //JSON return + var json = JSON.parse(response); + if (Object.keys(json).length === 0) { + return ""; + } + if (Array.isArray(json)) { + for (var i = 0; i < Object.keys(json).length; i++) { + returnResponse[returnResponse.length] = { "Value": json[i], "Label": this._Highlight(json[i]) }; + } + } + else { + for (var value in json) { + returnResponse.push({ + "Value": value, + "Label": this._Highlight(json[value]) + }); + } + } + return returnResponse; + } + catch (event) { + //HTML return + return response; + } + }, + /** + * Return the autocomplete value to send (before request) + */ + _Pre: function () { + return this.Input.value; + }, + /** + * Choice one result item + */ + _Select: function (item) { + if (item.hasAttribute("data-autocomplete-value")) { + this.Input.value = item.getAttribute("data-autocomplete-value"); + } + else { + this.Input.value = item.innerHTML; + } + this.Input.setAttribute("data-autocomplete-old-value", this.Input.value); + }, + $AjaxTimer: null, + $Listeners: {} + }; + return AutoComplete; +}()); +module.exports = AutoComplete; + +},{}]},{},[1])(1) +}); \ No newline at end of file diff --git a/pdb-ui/src/main/resources/resources/js/autocomplete.min.js b/pdb-ui/src/main/resources/resources/js/autocomplete.min.js new file mode 100644 index 0000000..fb22516 --- /dev/null +++ b/pdb-ui/src/main/resources/resources/js/autocomplete.min.js @@ -0,0 +1,14 @@ +/* + * @license MIT + * + * Autocomplete.js v2.1.0 + * Developed by Baptiste Donaux + * https://autocomplete-js.com + * + * (c) 2016, Baptiste Donaux + */ + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AutoComplete = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=e.From&&i.keyCode<=e.To?!e.Not:e.Not))};for(var r in e.KeyboardMappings){var n=t.merge({Operator:ConditionOperator.AND},e.KeyboardMappings[r]),s=ConditionOperator.AND==n.Operator;n.Conditions.forEach(o),s===!0&&n.Callback.bind(e,i)()}},t.prototype.ajax=function(e,i,o){if(void 0===o&&(o=!0),e.$AjaxTimer&&window.clearTimeout(e.$AjaxTimer),o===!0)e.$AjaxTimer=window.setTimeout(t.prototype.ajax.bind(null,e,i,!1),e.Delay);else{e.Request&&e.Request.abort();var r=Object.getOwnPropertyNames(e.HttpHeaders),n=e._HttpMethod(),s=e._Url(),a=e._QueryArg()+"="+e._Pre();n.match(/^GET$/i)&&(s+="?"+a),e.Request=new XMLHttpRequest,e.Request.open(n,s,!0);for(var u=r.length-1;u>=0;u--)e.Request.setRequestHeader(r[u],e.HttpHeaders[r[u]]);e.Request.onreadystatechange=i,e.Request.send(a)}},t.prototype.destroy=function(t){for(var e in t.$Listeners)t.Input.removeEventListener(e,t.$Listeners[e]);t.DOMResults.parentNode.removeChild(t.DOMResults)},t.merge=function(){for(var t,e={},i=0;i"+t+""}},HttpHeaders:{"Content-type":"application/x-www-form-urlencoded"},Limit:0,HttpMethod:"GET",QueryArg:"q",Url:null,KeyboardMappings:{Enter:{Conditions:[{Is:13,Not:!1}],Callback:function(){if(this.DOMResults.getAttribute("class").indexOf("open")!=-1){var t=this.DOMResults.querySelector("li.active");null!==t&&(this._Select(t),this.DOMResults.setAttribute("class","autocomplete"))}},Operator:ConditionOperator.AND},KeyUpAndDown:{Conditions:[{Is:38,Not:!1},{Is:40,Not:!1}],Callback:function(t){t.preventDefault();var e=this.DOMResults.querySelector("li:first-child:not(.locked)"),i=this.DOMResults.querySelector("li:last-child:not(.locked)"),o=this.DOMResults.querySelector("li.active");if(o){var r=Array.prototype.indexOf.call(o.parentNode.children,o),n=r+(t.keyCode-39),s=this.DOMResults.getElementsByTagName("li").length;n<0?n=s-1:n>=s&&(n=0),o.setAttribute("class",""),o.parentElement.childNodes.item(n).setAttribute("class","active")}else i&&38==t.keyCode?i.setAttribute("class","active"):e&&e.setAttribute("class","active")},Operator:ConditionOperator.OR},AlphaNum:{Conditions:[{Is:13,Not:!0},{From:35,To:40,Not:!0}],Callback:function(){var e=this.Input.getAttribute("data-autocomplete-old-value"),i=this._Pre();""!==i&&(e&&i==e||this.DOMResults.setAttribute("class","autocomplete open"),t.prototype.ajax(this,function(){4==this.Request.readyState&&200==this.Request.status&&(this._Render(this._Post(this.Request.response)),this._Open())}.bind(this)))},Operator:ConditionOperator.AND}},DOMResults:null,Request:null,Input:null,_EmptyMessage:function(){var t="";return t=this.Input.hasAttribute("data-autocomplete-empty-message")?this.Input.getAttribute("data-autocomplete-empty-message"):this.EmptyMessage!==!1?this.EmptyMessage:""},_Limit:function(){var t=this.Input.getAttribute("data-autocomplete-limit");return isNaN(t)?this.Limit:parseInt(t)},_Highlight:function(t){return t.replace(this.Highlight.getRegex(this._Pre()),this.Highlight.transform)},_HttpMethod:function(){return this.Input.hasAttribute("data-autocomplete-method")?this.Input.getAttribute("data-autocomplete-method"):this.HttpMethod},_QueryArg:function(){return this.Input.hasAttribute("data-autocomplete-param-name")?this.Input.getAttribute("data-autocomplete-param-name"):this.QueryArg},_Url:function(){return this.Input.hasAttribute("data-autocomplete")?this.Input.getAttribute("data-autocomplete"):this.Url},_Blur:function(t){if(void 0===t&&(t=!1),t)this.DOMResults.setAttribute("class","autocomplete");else{var e=this;setTimeout(function(){e._Blur(!0)},150)}},_Focus:function(){var t=this.Input.getAttribute("data-autocomplete-old-value");t&&this.Input.value==t||this.DOMResults.setAttribute("class","autocomplete open")},_Open:function(){var t=this;Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"),function(e){"locked"!=e.getAttribute("class")&&(e.onclick=function(){t._Select(e)})})},_Position:function(){this.DOMResults.setAttribute("class","autocomplete"),this.DOMResults.setAttribute("style","top:"+(this.Input.offsetTop+this.Input.offsetHeight)+"px;left:"+this.Input.offsetLeft+"px;width:"+this.Input.clientWidth+"px;")},_Render:function(t){var e;e="string"==typeof t?this._RenderRaw(t):this._RenderResponseItems(t),this.DOMResults.hasChildNodes()&&this.DOMResults.removeChild(this.DOMResults.childNodes[0]),this.DOMResults.appendChild(e)},_RenderResponseItems:function(t){var e=document.createElement("ul"),i=document.createElement("li");this._Limit()<0&&(t=t.reverse());for(var o=0;o0)this.DOMResults.innerHTML=t;else{var o=this._EmptyMessage();""!==o&&(i.innerHTML=o,i.setAttribute("class","locked"),e.appendChild(i))}return e},_Post:function(t){try{var e=[],i=JSON.parse(t);if(0===Object.keys(i).length)return"";if(Array.isArray(i))for(var o=0;o'); - }; - var error = function(e) { - //var response = JSON.parse(e.responseText); - $('#result-view').text("FAILED: " + JSON.stringify(e)); - }; - - - postJson("plots", request, success, error); - + $('#search-submit').click(plot); + + AutoComplete({ + HttpMethod: "GET", + Delay: 300, + _QueryArg: function() { + var caretIndex = document.getElementById('search-input').selectionStart; + return 'caretIndex=' + caretIndex + '&query'; + }, + _Post: function(response) { + var result = []; + var responseObject = JSON.parse(response); + responseObject['proposals'].forEach(function(item, index){ + var proposal = {}; + proposal['Label'] = item.value; + proposal['Value'] = item.proposedQuery; + + result.push(proposal); + }); + + console.log(JSON.stringify(result)); + return result; + } }); }); @@ -31,6 +33,28 @@ function showLoadingIcon() } +function plot(event){ + event.preventDefault(); // prevent submit of form which would reload the page + + showLoadingIcon(); + var request = {}; + request['query'] = $('#search-input').val(); + request['height'] = $('#result-view').height()-10; + request['width'] = $('#result-view').width()-10; + + var success = function(response){ + + $('#result-view').html(''); + }; + var error = function(e) { + $('#result-view').text("FAILED: " + JSON.stringify(e)); + }; + + + postJson("plots", request, success, error); + +} + function postJson(url, requestData, successCallback, errorCallback) { $.ajax({ @@ -41,7 +65,6 @@ function postJson(url, requestData, successCallback, errorCallback) { }) .done(successCallback) .fail(errorCallback); - } diff --git a/performanceDb/build.gradle b/performanceDb/build.gradle index e0eede4..87552fa 100644 --- a/performanceDb/build.gradle +++ b/performanceDb/build.gradle @@ -1,7 +1,7 @@ dependencies { compile project(':pdb-api') - compile 'org.lucares:ludb:1.0.20161217184921' + compile 'org.lucares:ludb:1.0.20161223090600' compile 'com.fasterxml.jackson.core:jackson-databind:2.8.5' compile 'org.mapdb:mapdb:3.0.2' } diff --git a/performanceDb/src/main/java/org/lucares/performance/db/CollectionUtils.java b/performanceDb/src/main/java/org/lucares/performance/db/CollectionUtils.java index 8382070..7d3d45d 100644 --- a/performanceDb/src/main/java/org/lucares/performance/db/CollectionUtils.java +++ b/performanceDb/src/main/java/org/lucares/performance/db/CollectionUtils.java @@ -1,5 +1,6 @@ package org.lucares.performance.db; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,7 +22,7 @@ public interface CollectionUtils { return result; } - public default List filter(final List list, final Predicate predicate) { + public default List filter(final Collection list, final Predicate predicate) { return list.stream().filter(predicate).collect(Collectors.toList()); } } diff --git a/performanceDb/src/main/java/org/lucares/performance/db/PerformanceDb.java b/performanceDb/src/main/java/org/lucares/performance/db/PerformanceDb.java index 203c753..7f8faed 100644 --- a/performanceDb/src/main/java/org/lucares/performance/db/PerformanceDb.java +++ b/performanceDb/src/main/java/org/lucares/performance/db/PerformanceDb.java @@ -1,5 +1,6 @@ package org.lucares.performance.db; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.time.OffsetDateTime; @@ -15,6 +16,10 @@ import java.util.logging.Logger; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.lucares.ludb.Field; +import org.lucares.ludb.FieldType; +import org.lucares.ludb.H2DB; +import org.lucares.ludb.Proposal; import org.lucares.pdb.api.Entry; import org.lucares.pdb.api.GroupResult; import org.lucares.pdb.api.Result; @@ -26,8 +31,18 @@ public class PerformanceDb implements AutoCloseable { private final TagsToFile tagsToFile; + private final H2DB db; + public PerformanceDb(final Path dataDirectory) { - tagsToFile = new TagsToFile(dataDirectory); + + db = new H2DB(new File(dataDirectory.toFile(), "lu.db")); + + final Field dateOffsetField = db.getField(Fields.DATE_OFFSET); + if (dateOffsetField == null) { + db.createField(Fields.DATE_OFFSET, FieldType.STRING); + } + + tagsToFile = new TagsToFile(dataDirectory, db); } public void put(final Entry entry) throws WriteException { @@ -166,6 +181,15 @@ public class PerformanceDb implements AutoCloseable { @Override public void close() { - tagsToFile.close(); + try { + db.close(); + } catch (final Exception e) { + // H2 doesn't actually do anything in close + throw new IllegalStateException(e); + } + } + + public List autocomplete(final String query, final int caretIndex) { + return db.proposeTagForQuery(query, caretIndex); } } diff --git a/performanceDb/src/main/java/org/lucares/performance/db/TagsToFile.java b/performanceDb/src/main/java/org/lucares/performance/db/TagsToFile.java index 65a8e34..36e0989 100644 --- a/performanceDb/src/main/java/org/lucares/performance/db/TagsToFile.java +++ b/performanceDb/src/main/java/org/lucares/performance/db/TagsToFile.java @@ -14,26 +14,18 @@ import java.util.UUID; import org.lucares.ludb.Document; import org.lucares.ludb.Field; -import org.lucares.ludb.FieldExistsException; import org.lucares.ludb.FieldType; import org.lucares.ludb.H2DB; import org.lucares.pdb.api.Tags; -public class TagsToFile implements AutoCloseable, CollectionUtils { +public class TagsToFile implements CollectionUtils { private final H2DB db; private final Path dataDirectory; - public TagsToFile(final Path dataDirectory) { - super(); + public TagsToFile(final Path dataDirectory, final H2DB db) { this.dataDirectory = dataDirectory; - db = new H2DB(new File(dataDirectory.toFile(), "lu.db")); - try { - db.createField(Fields.DATE_OFFSET, FieldType.STRING); - } catch (final FieldExistsException e) { - // TODO @ahr ludb needs a hasField method, or a - // createIfNotExists - } + this.db = db; } private List getFilesMatchingTagsExactly(final Tags tags) { @@ -59,8 +51,7 @@ public class TagsToFile implements AutoCloseable, CollectionUtils { result.add(pdbFile); } } catch (final NullPointerException e) { - // TODO @ahr ludb should handle unknown fields better - e.printStackTrace(); + // TODO @ahr unknown fields in searches must be handled better } Collections.sort(result, PdbFileByTimeAsc.INSTANCE); @@ -129,18 +120,18 @@ public class TagsToFile implements AutoCloseable, CollectionUtils { ensureFieldsExist(tags); - tags.forEach((key, value) -> { - db.setProperty(file, key, value); + tags.forEach((fieldName, value) -> { + TagsUtils.setProperty(db, file, fieldName, value); }); - db.setProperty(file, Fields.DATE_OFFSET, day.serialize()); + TagsUtils.setProperty(db, file, Fields.DATE_OFFSET, day.serialize()); result = new PdbFile(day, file, tags); return result; } private void ensureFieldsExist(final Tags tags) { - final List fields = db.getAvailableFields(); + final List fields = db.getAvailableFields(""); final Map fieldsMap = toMap(fields, Field::getName); tags.forEach((key, value) -> { @@ -166,15 +157,4 @@ public class TagsToFile implements AutoCloseable, CollectionUtils { return result; } - - @Override - public void close() { - try { - db.close(); - } catch (final Exception e) { - // H2 doesn't actually do anything in close - throw new IllegalStateException(e); - } - } - } diff --git a/performanceDb/src/main/java/org/lucares/performance/db/TagsUtils.java b/performanceDb/src/main/java/org/lucares/performance/db/TagsUtils.java index e25c9c9..ecad3e3 100644 --- a/performanceDb/src/main/java/org/lucares/performance/db/TagsUtils.java +++ b/performanceDb/src/main/java/org/lucares/performance/db/TagsUtils.java @@ -1,5 +1,10 @@ package org.lucares.performance.db; +import java.io.File; + +import org.lucares.ludb.FieldNotExistsException; +import org.lucares.ludb.FieldType; +import org.lucares.ludb.H2DB; import org.lucares.pdb.api.Tags; class TagsUtils { @@ -10,4 +15,17 @@ class TagsUtils { } }); } + + static void setProperty(final H2DB db, final File file, final String fieldName, final String value) { + try { + db.setProperty(file, fieldName, value); + } catch (final FieldNotExistsException e) { + db.createField(fieldName, FieldType.STRING); + try { + db.setProperty(file, fieldName, value); + } catch (final FieldNotExistsException e1) { + throw new IllegalStateException(e1); + } + } + } } diff --git a/performanceDb/src/main/java/org/lucares/performance/db/TimeRange.java b/performanceDb/src/main/java/org/lucares/performance/db/TimeRange.java index 42968f7..ebf4806 100644 --- a/performanceDb/src/main/java/org/lucares/performance/db/TimeRange.java +++ b/performanceDb/src/main/java/org/lucares/performance/db/TimeRange.java @@ -32,7 +32,6 @@ public class TimeRange { } public boolean inRange(final long epochMilli) { - // TODO @ahr cache the epoch millis final long fromEpochMilli = from.toInstant().toEpochMilli(); final long toEpochMilli = to.toInstant().toEpochMilli(); diff --git a/performanceDb/src/test/java/org/lucares/performance/db/ObjectMapperTest.java b/performanceDb/src/test/java/org/lucares/performance/db/ObjectMapperTest.java index b041e78..2044288 100644 --- a/performanceDb/src/test/java/org/lucares/performance/db/ObjectMapperTest.java +++ b/performanceDb/src/test/java/org/lucares/performance/db/ObjectMapperTest.java @@ -19,7 +19,6 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.lucares.ludb.FieldType; import org.lucares.ludb.H2DB; import org.mapdb.DB; import org.mapdb.DBMaker; @@ -127,12 +126,7 @@ public class ObjectMapperTest { db.addDocument(file); tagEntry.getTags().forEach((k, v) -> { - try { - db.setProperty(file, k, v); - } catch (final NullPointerException e) { - db.createField(k, FieldType.STRING); - db.setProperty(file, k, v); - } + TagsUtils.setProperty(db, file, k, v); }); } diff --git a/performanceDb/src/test/java/org/lucares/performance/db/TagsToFilesTest.java b/performanceDb/src/test/java/org/lucares/performance/db/TagsToFilesTest.java index 3ec65cc..4ca9213 100644 --- a/performanceDb/src/test/java/org/lucares/performance/db/TagsToFilesTest.java +++ b/performanceDb/src/test/java/org/lucares/performance/db/TagsToFilesTest.java @@ -1,11 +1,14 @@ package org.lucares.performance.db; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.OffsetDateTime; import java.time.ZoneOffset; +import org.lucares.ludb.FieldType; +import org.lucares.ludb.H2DB; import org.lucares.pdb.api.Tags; import org.testng.Assert; import org.testng.annotations.AfterMethod; @@ -28,7 +31,12 @@ public class TagsToFilesTest { } public void test() throws Exception { - try (final TagsToFile tagsToFile = new TagsToFile(dataDirectory)) { + + try (H2DB db = new H2DB(new File(dataDirectory.toFile(), "lu.db"))) { + + db.createField(Fields.DATE_OFFSET, FieldType.STRING); + + final TagsToFile tagsToFile = new TagsToFile(dataDirectory, db); final OffsetDateTime date = OffsetDateTime.now(ZoneOffset.UTC); final Tags tags = Tags.create("myKey", "myValue");