diff --git a/pillar/api/search/queries.py b/pillar/api/search/queries.py index 1a94a955..f21f7ca4 100644 --- a/pillar/api/search/queries.py +++ b/pillar/api/search/queries.py @@ -2,33 +2,57 @@ import logging import json from elasticsearch import Elasticsearch from elasticsearch_dsl import Search, Q -from elasticsearch_dsl.connections import connections from pillar import current_app -#elk_hosts = current_app.config['ELASTIC_SEARCH_HOSTS'] -# -#connections.create_connection( -# hosts=elk_hosts, -# sniff_on_start=True, -# timeout=20) -# client = Elasticsearch() log = logging.getLogger(__name__) +node_agg_terms = ['node_type', 'media', 'tags', 'is_free'] +user_agg_terms = ['roles', ] -def add_aggs_to_search(search): +def add_aggs_to_search(search, agg_terms): """ + Add facets / aggregations to the search result """ - agg_terms = ['node_type', 'media', 'tags', 'is_free'] - for term in agg_terms: search.aggs.bucket(term, 'terms', field=term) - #search.aggs.bucket('project', 'terms', field='project.name') + +def make_must(terms): + """ + Given some term parameters + we must match those + """ + + must = [] + + for field, value in terms.items(): + + print(field, value) + + if value: + must.append({'match': {field: value}}) + + return must + + +def nested_bool(should, terms): + """ + """ + must = [] + must = make_must(terms) + bool_query = Q('bool', should=should) + must.append(bool_query) + bool_query = Q('bool', must=must) + + search = Search(using=client) + search.query = bool_query + + return search def do_search(query: str, terms: dict) -> dict: @@ -46,16 +70,14 @@ def do_search(query: str, terms: dict) -> dict: Q('term', tags=query), ] - #must = [] + if query: + search = nested_bool(should, terms) + else: + # do a match all for the aggregations + search = Search(using=client) + search.query = Q('term', _type='node') - #for field, value in terms.items(): - # must.append( - - bool_query = Q('bool', should=should) - search = Search(using=client) - search.query = bool_query - - add_aggs_to_search(search) + add_aggs_to_search(search, node_agg_terms) if current_app.config['DEBUG']: print(json.dumps(search.to_dict(), indent=4)) @@ -68,7 +90,7 @@ def do_search(query: str, terms: dict) -> dict: return response.to_dict() -def do_user_search(query: str) -> dict: +def do_user_search(query: str, terms: dict) -> dict: """ return user objects """ @@ -76,17 +98,23 @@ def do_user_search(query: str) -> dict: Q('match', username=query), Q('match', full_name=query), ] - bool_query = Q('bool', should=should) - search = Search(using=client) - search.query = bool_query + + if query: + search = nested_bool(should, terms) + else: + # do a match all for the aggregations + search = Search(using=client) + search.query = Q('term', _type='user') + + add_aggs_to_search(search, user_agg_terms) if current_app.config['DEBUG']: - log.debug(json.dumps(search.to_dict(), indent=4)) + print(json.dumps(search.to_dict(), indent=4)) response = search.execute() if current_app.config['DEBUG']: - log.debug('%s', json.dumps(response.to_dict(), indent=4)) + print(json.dumps(response.to_dict(), indent=4)) return response.to_dict() diff --git a/pillar/api/search/routes.py b/pillar/api/search/routes.py index 378a4fa4..a3f8a9e3 100644 --- a/pillar/api/search/routes.py +++ b/pillar/api/search/routes.py @@ -1,22 +1,15 @@ -import json import logging -from bson import ObjectId -from flask import Blueprint, request, current_app, make_response, url_for -from flask import Response +from flask import Blueprint, request from werkzeug import exceptions as wz_exceptions +from pillar.api.utils import authorization, jsonify -from pillar.api.utils import authorization, jsonify, str2id -from pillar.api.utils import mongo -from pillar.api.utils.authorization import require_login, check_permissions -from pillar.auth import current_user - +from . import queries log = logging.getLogger(__name__) blueprint_search = Blueprint('elksearch', __name__) -from . import queries def _valid_search() -> str: @@ -25,19 +18,23 @@ def _valid_search() -> str: """ searchword = request.args.get('q', '') - if not searchword: - raise wz_exceptions.BadRequest('You are forgetting a "?q=whatareyoulookingfor"') + # if not searchword: + # raise wz_exceptions.BadRequest( + # 'You are forgetting a "?q=whatareyoulookingfor"') return searchword def _term_filters() -> dict: """ - Check if frontent want to filter stuff + Check if frontent wants to filter stuff + on specific fields AKA facets """ terms = [ 'node_type', 'media', - 'tags', 'is_free', 'projectname'] + 'tags', 'is_free', 'projectname', + 'roles', + ] parsed_terms = {} @@ -60,7 +57,8 @@ def search_user(): searchword = _valid_search() - data = queries.do_user_search(searchword) + terms = _term_filters() + data = queries.do_user_search(searchword, terms) return jsonify(data) diff --git a/src/scripts/algolia_search.js b/src/scripts/algolia_search.js index b537d901..cee0ea5c 100644 --- a/src/scripts/algolia_search.js +++ b/src/scripts/algolia_search.js @@ -8,7 +8,9 @@ $(document).ready(function() { var $hits = $('#hits'); var $stats = $('#stats'); var $facets = $('#facets'); + //var facets = []; var $pagination = $('#pagination'); + var what = ''; // Templates binding var hitTemplate = Hogan.compile($('#hit-template').text()); @@ -19,6 +21,7 @@ $(document).ready(function() { // something elasticy! var search = elasticSearcher; + // facets: $.map(FACET_CONFIG, function(facet) { // return !facet.disjunctive ? facet.name : null; // }), @@ -137,17 +140,23 @@ $(document).ready(function() { // If no results if (content.hits.length === 0) { $facets.empty(); + facets =[]; return; } var storeValue = function (values, label){ + return function(item){ + + let refined = search.isRefined(label, item.key); + values.push({ facet: label, label: item.key, value: item.key, count: item.doc_count, + refined: refined, }); }; }; @@ -164,6 +173,8 @@ $(document).ready(function() { if (buckets.length === 0) { continue; } + + buckets.forEach(storeValue(values, label)); facets.push({ @@ -252,7 +263,7 @@ $(document).ready(function() { }); $(document).on('click', '.toggleRefine', function() { - search.addTerm($(this).data('facet'), $(this).data('value')); + search.toggleTerm($(this).data('facet'), $(this).data('value')); search.execute(); return false; }); @@ -277,6 +288,7 @@ $(document).ready(function() { $('#facets').on("mouseenter mouseleave", ".button-checkbox", function(e) { $(this).parent().find('.facet_link').toggleClass("hover"); }); + $('#facets').on("mouseenter mouseleave", ".facet_link", function(e) { $(this).parent().find('.button-checkbox button.btn').toggleClass("hover"); }); @@ -330,7 +342,7 @@ $(document).ready(function() { } var query = decodeURIComponent(sURLVariables[0].split('=')[1]); $inputField.val(query); - search.setQuery(query); + search.setQuery(query, what); for (var i = 2; i < sURLVariables.length; i++) { var sParameterName = sURLVariables[i].split('='); @@ -362,4 +374,8 @@ $(document).ready(function() { location.replace(urlParams); } + // do empty search to fill aggregations + search.setQuery('', what); + search.execute(); + }); diff --git a/src/scripts/tutti/4_search.js b/src/scripts/tutti/4_search.js index 79cb2ab9..ba075040 100644 --- a/src/scripts/tutti/4_search.js +++ b/src/scripts/tutti/4_search.js @@ -14,7 +14,7 @@ var elasticSearcher = (function() { page: 0, setQuery: (function(q, _url){ - console.log('setQuery!: ' + q); + console.log('setQuery!: ' + q + 'what :'+ _url); deze.query=q; if (_url !== undefined) { deze.url=_url; @@ -45,8 +45,19 @@ var elasticSearcher = (function() { return deze.newhits.aggregations; }), - addTerm: (function(term, value){ - deze.terms[term] = value; + toggleTerm: (function(term, value){ + if (deze.terms[term] !== undefined) { + delete deze.terms[term]; + } else { + deze.terms[term] = value; + } + }), + + isRefined: (function(term, value){ + if (deze.terms[term] === value) { + return true; + } + return false; }), //get response from elastic and rebuild json @@ -89,7 +100,8 @@ var elasticSearcher = (function() { setCurrentPage: deze.setCurrentPage, query: deze.query, page: deze.page, - addTerm: deze.addTerm, + toggleTerm: deze.toggleTerm, + isRefined: deze.isRefined, }; })(); diff --git a/src/scripts/tutti/7_user_search.js b/src/scripts/tutti/7_user_search.js index 466d3fc0..b14f9626 100644 --- a/src/scripts/tutti/7_user_search.js +++ b/src/scripts/tutti/7_user_search.js @@ -6,8 +6,8 @@ this.autocomplete({hint: false}, [ { source: elasticSearch($, '/user'), - displayKey: 'full_name', - async: true, + displayKey: 'full_name', + async: true, minLength: 1, limit: 10, templates: { diff --git a/src/templates/users/index.pug b/src/templates/users/index.pug index 524c1524..6c5eef8f 100644 --- a/src/templates/users/index.pug +++ b/src/templates/users/index.pug @@ -22,6 +22,7 @@ style. type="text", name="q", id="q", + what="/user", autocomplete="off", spellcheck="false", autocorrect="false", @@ -92,14 +93,6 @@ script(type="text/template", id="stats-template") | {% endblock %} | {% block footer_scripts %} -script(). - var APPLICATION_ID = '{{config.ALGOLIA_USER}}'; - var SEARCH_ONLY_API_KEY = '{{config.ALGOLIA_PUBLIC_KEY}}'; - var INDEX_NAME = '{{config.ALGOLIA_INDEX_USERS}}'; - var sortByCountDesc = null; - var FACET_CONFIG = [ - { name: 'roles', title: 'Roles', disjunctive: false, sortFunction: sortByCountDesc }, - ]; script(src="{{ url_for('static_pillar', filename='assets/js/vendor/algoliasearch-3.19.0.min.js')}}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/algoliasearch.helper.min.js') }}")