Implement query strings for shareable search links

This commit is contained in:
Mark Veidemanis 2022-08-10 09:27:01 +01:00
parent c1071f3d55
commit 648526a6bf
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
3 changed files with 225 additions and 79 deletions

View File

@ -141,7 +141,7 @@ def run_main_query(client, user, query, custom_query=False, index=None, size=Non
return response return response
def query_results(request, size=None): def query_results(request, query_params, size=None):
""" """
API helper to alter the OpenSearch return format into something API helper to alter the OpenSearch return format into something
a bit better to parse. a bit better to parse.
@ -160,14 +160,14 @@ def query_results(request, size=None):
else: else:
sizes = settings.OPENSEARCH_MAIN_SIZES sizes = settings.OPENSEARCH_MAIN_SIZES
if not size: if not size:
if "size" in request.POST: if "size" in query_params:
size = request.POST["size"] size = query_params["size"]
if size not in sizes: if size not in sizes:
message = "Size is not permitted" message = "Size is not permitted"
message_class = "danger" message_class = "danger"
return {"message": message, "class": message_class} return {"message": message, "class": message_class}
if "source" in request.POST: if "source" in query_params:
source = request.POST["source"] source = query_params["source"]
if source not in settings.OPENSEARCH_MAIN_SOURCES: if source not in settings.OPENSEARCH_MAIN_SOURCES:
message = "Invalid source" message = "Invalid source"
message_class = "danger" message_class = "danger"
@ -175,16 +175,13 @@ def query_results(request, size=None):
if source != "all": if source != "all":
add_bool.append({"src": source}) add_bool.append({"src": source})
if "dates" in request.POST: if set({"from_date", "to_date", "from_time", "to_time"}).issubset(
dates = request.POST["dates"] query_params.keys()
spl = dates.split(" - ") ):
if all(spl): from_ts = f"{query_params['from_date']}T{query_params['from_time']}Z"
spl = [f"{x.replace(' ', 'T')}Z" for x in spl] to_ts = f"{query_params['to_date']}T{query_params['to_time']}Z"
if not len(spl) == 2: print("from ts", from_ts)
message = "Invalid dates" print("to_ts", to_ts)
message_class = "danger"
return {"message": message, "class": message_class}
from_ts, to_ts = spl
range_query = { range_query = {
"range": { "range": {
"ts": { "ts": {
@ -194,8 +191,8 @@ def query_results(request, size=None):
} }
} }
add_top.append(range_query) add_top.append(range_query)
if "sorting" in request.POST: if "sorting" in query_params:
sorting = request.POST["sorting"] sorting = query_params["sorting"]
if sorting not in ("asc", "desc", "none"): if sorting not in ("asc", "desc", "none"):
message = "Invalid sort" message = "Invalid sort"
message_class = "danger" message_class = "danger"
@ -209,20 +206,20 @@ def query_results(request, size=None):
} }
] ]
if "check-sentiment" in request.POST: if "check_sentiment" in query_params:
if "sentiment-method" not in request.POST: if "sentiment_method" not in query_params:
message = "No sentiment method" message = "No sentiment method"
message_class = "danger" message_class = "danger"
return {"message": message, "class": message_class} return {"message": message, "class": message_class}
if "sentiment" in request.POST: if "sentiment" in query_params:
sentiment = request.POST["sentiment"] sentiment = query_params["sentiment"]
try: try:
sentiment = float(sentiment) sentiment = float(sentiment)
except ValueError: except ValueError:
message = "Sentiment is not a float" message = "Sentiment is not a float"
message_class = "danger" message_class = "danger"
return {"message": message, "class": message_class} return {"message": message, "class": message_class}
sentiment_method = request.POST["sentiment-method"] sentiment_method = query_params["sentiment_method"]
range_query_compare = {"range": {"sentiment": {}}} range_query_compare = {"range": {"sentiment": {}}}
range_query_precise = { range_query_precise = {
"match": { "match": {
@ -242,8 +239,8 @@ def query_results(request, size=None):
range_query_precise["match"]["sentiment"] = 0 range_query_precise["match"]["sentiment"] = 0
add_top_negative.append(range_query_precise) add_top_negative.append(range_query_precise)
if "query" in request.POST: if "query" in query_params:
query = request.POST["query"] query = query_params["query"]
search_query = construct_query(query, size) search_query = construct_query(query, size)
if add_bool: if add_bool:
for item in add_bool: for item in add_bool:

View File

@ -81,7 +81,7 @@
hx-post="{% url 'home' %}" hx-post="{% url 'home' %}"
hx-trigger="keyup changed delay:200ms" hx-trigger="keyup changed delay:200ms"
hx-target="#results" hx-target="#results"
hx-swap="innerHTML" id="query" name="query" class="input" type="text" placeholder="msg: science AND nick: BillNye AND channel: #science"> hx-swap="innerHTML" id="query" name="query" value="{{ params.query }}" class="input" type="text" placeholder="msg: science AND nick: BillNye AND channel: #science">
<span class="icon is-small is-left"> <span class="icon is-small is-left">
<i class="fas fa-magnifying-glass"></i> <i class="fas fa-magnifying-glass"></i>
</span> </span>
@ -122,7 +122,11 @@
<span class="select"> <span class="select">
<select name="size"> <select name="size">
{% for size in sizes %} {% for size in sizes %}
{% if size == params.size %}
<option selected value="{{ size }}">{{ size }}</option>
{% else %}
<option value="{{ size }}">{{ size }}</option> <option value="{{ size }}">{{ size }}</option>
{% endif %}
{% endfor %} {% endfor %}
</select> </select>
<span class="icon is-small is-left"> <span class="icon is-small is-left">
@ -142,9 +146,26 @@
<div class="control has-icons-left"> <div class="control has-icons-left">
<span class="select"> <span class="select">
<select id="source" name="source"> <select id="source" name="source">
<option selected value="all">All</option> {% if params.source == 'irc' %}
<option selected value="irc">IRC</option>
{% else %}
<option value="irc">IRC</option> <option value="irc">IRC</option>
{% endif %}
{% if params.source == 'dis' %}
<option selected value="dis">Discord</option>
{% else %}
<option value="dis">Discord</option> <option value="dis">Discord</option>
{% endif %}
{% if params.source == None %}
<option selected value="all">All</option>
{% elif params.source == 'all' %}
<option selected value="all">All</option>
{% else %}
<option value="all">All</option>
{% endif %}
</select> </select>
<span class="icon is-small is-left"> <span class="icon is-small is-left">
<i class="fas fa-magnifying-glass"></i> <i class="fas fa-magnifying-glass"></i>
@ -162,8 +183,24 @@
<div id="sentiment"> <div id="sentiment">
<div class="field has-addons"> <div class="field has-addons">
<div class="control"> <div class="control">
<input disabled="undefined" name="sentiment" id="sliderWithValue" class="slider has-output-tooltip is-fullwidth" min="-1" max="1" value="0" step="0.05" type="range"> <input
<output for="sliderWithValue" class="slider-output">0</output> {% if params.check_sentiment != "on" %}
disabled="undefined"
{% endif %}
name="sentiment" id="sliderWithValue" class="slider has-output-tooltip is-fullwidth" min="-1" max="1"
{% if params.sentiment == None %}
value="0"
{% else %}
value="{{ params.sentiment }}"
{% endif %}
step="0.05" type="range">
<output for="sliderWithValue" class="slider-output">
{% if params.sentiment == None %}
0
{% else %}
{{ params.sentiment }}
{% endif %}
</output>
<script>bulmaSlider.attach();</script> <script>bulmaSlider.attach();</script>
</div> </div>
<p class="control"> <p class="control">
@ -174,25 +211,47 @@
</div> </div>
<div class="control"> <div class="control">
<label class="radio button has-text-link"> <label class="radio button has-text-link">
<input type="radio" value="below" name="sentiment-method">
<input type="radio"
value="below"
{% if params.sentiment_method == 'below' %}
checked
{% endif %}
name="sentiment_method">
<span class="icon" data-tooltip="Below"> <span class="icon" data-tooltip="Below">
<i class="fa-solid fa-face-frown"></i> <i class="fa-solid fa-face-frown"></i>
</span> </span>
</label> </label>
<label class="radio button has-text-link is-hidden"> <label class="radio button has-text-link is-hidden">
<input type="radio" value="exact" name="sentiment-method">
<input type="radio"
value="exact"
{% if params.sentiment_method == 'exact' %}
checked
{% endif %}
name="sentiment_method">
<span class="icon" data-tooltip="Exact"> <span class="icon" data-tooltip="Exact">
<i class="fa-solid fa-face-smile"></i> <i class="fa-solid fa-face-smile"></i>
</span> </span>
</label> </label>
<label class="radio button has-text-link"> <label class="radio button has-text-link">
<input type="radio" value="above" name="sentiment-method"> <input type="radio"
value="above"
{% if params.sentiment_method == 'above' %}
checked
{% endif %}
name="sentiment_method">
<span class="icon" data-tooltip="Above"> <span class="icon" data-tooltip="Above">
<i class="fa-solid fa-face-smile"></i> <i class="fa-solid fa-face-smile"></i>
</span> </span>
</label> </label>
<label class="radio button has-text-link"> <label class="radio button has-text-link">
<input type="radio" value="nonzero" name="sentiment-method"> <input type="radio"
value="nonzero"
{% if params.sentiment_method == 'nonzero' %}
checked
{% endif %}
name="sentiment_method">
<span class="icon" data-tooltip="Nonzero"> <span class="icon" data-tooltip="Nonzero">
<i class="fa-solid fa-face-meh-blank"></i> <i class="fa-solid fa-face-meh-blank"></i>
</span> </span>
@ -201,7 +260,11 @@
</div> </div>
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" name="check-sentiment" <input type="checkbox"
name="check_sentiment"
{% if params.check_sentiment == "on" %}
checked
{% endif %}
data-script="on click toggle @disabled on #sliderWithValue then toggle @disabled on #sentiment"> data-script="on click toggle @disabled on #sliderWithValue then toggle @disabled on #sentiment">
Check sentiment Check sentiment
</label> </label>
@ -210,7 +273,7 @@
<div id="date"> <div id="date">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input type="date" name="dates"> <input type="date" name="dates" value="{{ params.date }}">
<script> <script>
var options = { var options = {
"type": "datetime", "type": "datetime",
@ -218,6 +281,10 @@
"color": "info", "color": "info",
"validateLabel": "Save", "validateLabel": "Save",
"dateFormat": "yyyy-MM-dd", "dateFormat": "yyyy-MM-dd",
"startDate": "{{ params.from_date|escapejs }}",
"startTime": "{{ params.from_time|escapejs }}",
"endDate": "{{ params.to_date|escapejs }}",
"endTime": "{{ params.to_time|escapejs }}",
}; };
// Initialize all input of type date // Initialize all input of type date
var calendars = bulmaCalendar.attach('[type="date"]', options); var calendars = bulmaCalendar.attach('[type="date"]', options);
@ -234,19 +301,31 @@
</div> </div>
<div class="control"> <div class="control">
<label class="radio button has-text-link"> <label class="radio button has-text-link">
<input type="radio" value="desc" name="sorting" checked> <input type="radio" value="desc" name="sorting"
{% if params.sorting == None %}
checked
{% elif params.sorting == 'desc' %}
checked
{% endif %}
>
<span class="icon" data-tooltip="Sort descending"> <span class="icon" data-tooltip="Sort descending">
<i class="fa-solid fa-sort-down"></i> <i class="fa-solid fa-sort-down"></i>
</span> </span>
</label> </label>
<label class="radio button"> <label class="radio button">
<input type="radio" value="asc" name="sorting"> <input type="radio" value="asc" name="sorting"
{% if params.sorting == 'asc' %}
checked
{% endif %}>
<span class="icon" data-tooltip="Sort ascending"> <span class="icon" data-tooltip="Sort ascending">
<i class="fa-solid fa-sort-up"></i> <i class="fa-solid fa-sort-up"></i>
</span> </span>
</label> </label>
<label class="radio button"> <label class="radio button">
<input type="radio" value="none" name="sorting"> <input type="radio" value="none" name="sorting"
{% if params.sorting == 'none' %}
checked
{% endif %}>
<span class="icon" data-tooltip="No sort"> <span class="icon" data-tooltip="No sort">
<i class="fa-solid fa-sort"></i> <i class="fa-solid fa-sort"></i>
</span> </span>
@ -264,6 +343,9 @@
<script> <script>
var inputTags = document.getElementById('tags'); var inputTags = document.getElementById('tags');
new BulmaTagsInput(inputTags); new BulmaTagsInput(inputTags);
{% for tag in tags %}
inputTags.BulmaTagsInput().add("{{ tag|escapejs }}");
{% endfor %}
inputTags.BulmaTagsInput().on('before.add', function(item) { inputTags.BulmaTagsInput().on('before.add', function(item) {
if (item.includes(": ")) { if (item.includes(": ")) {
var spl = item.split(": "); var spl = item.split(": ");
@ -293,6 +375,9 @@
</div> </div>
<div class="block"> <div class="block">
<div id="results"> <div id="results">
{% if results %}
{% include 'ui/drilldown/results.html' %}
{% endif %}
</div> </div>
</div> </div>
<div id="modals-here"> <div id="modals-here">

View File

@ -46,32 +46,71 @@ class DrilldownTableView(View, SingleTableMixin):
return HttpResponse("No results") return HttpResponse("No results")
class Drilldown(View): def parse_dates(dates):
template_name = "ui/drilldown/drilldown.html" spl = dates.split(" - ")
plan_name = "drilldown" if all(spl):
spl = [f"{x.replace(' ', 'T')}" for x in spl]
if not len(spl) == 2:
message = "Invalid dates"
message_class = "danger"
return {"message": message, "class": message_class}
from_ts, to_ts = spl
from_date, from_time = from_ts.split("T")
to_date, to_time = to_ts.split("T")
def get(self, request): return {
if request.user.is_anonymous: "from_date": from_date,
sizes = settings.OPENSEARCH_MAIN_SIZES_ANON "to_date": to_date,
else: "from_time": from_time,
sizes = settings.OPENSEARCH_MAIN_SIZES "to_time": to_time,
context = {
"sizes": sizes,
} }
return render(request, self.template_name, context) else:
message = "Invalid dates"
message_class = "danger"
return {"message": message, "class": message_class}
def post(self, request):
def create_tags(query):
"""
Grab the tags out of the query and make a list
we can add to the Bulma tags element when the page loads.
"""
spl = query.split("AND")
spl = [x.strip() for x in spl if ":" in x]
spl = [x.replace('"', "") for x in spl]
return spl
def drilldown_search(request):
template_name = "ui/drilldown/results.html" template_name = "ui/drilldown/results.html"
data_args = request.POST.dict() if request.GET:
del data_args["csrfmiddlewaretoken"] query_params = request.GET.dict()
print("rep", repr(data_args["dates"])) elif request.POST:
if data_args["dates"] == " - ": query_params = request.POST.dict()
del data_args["dates"]
url_params = urllib.parse.urlencode(data_args) # Parse the dates
print("url_params", url_params) if "dates" in query_params:
context = query_results(request) dates = parse_dates(query_params["dates"])
del query_params["dates"]
if "message" in dates:
return render(request, template_name, dates)
query_params["from_date"] = dates["from_date"]
query_params["to_date"] = dates["to_date"]
query_params["from_time"] = dates["from_time"]
query_params["to_time"] = dates["to_time"]
if request.GET:
context = query_results(request, query_params)
elif request.POST:
context = query_results(request, query_params)
# Turn the query into tags for populating the taglist
tags = create_tags(query_params["query"])
context["tags"] = tags
context["params"] = query_params
if "message" in context: if "message" in context:
return render(request, self.template_name, context) return render(request, template_name, context)
context["data"] = json.dumps( context["data"] = json.dumps(
[ [
@ -86,12 +125,37 @@ class Drilldown(View):
) )
if context: if context:
response = render(request, template_name, context) response = render(request, template_name, context)
if request.GET:
return context
elif request.POST:
del query_params["csrfmiddlewaretoken"]
url_params = urllib.parse.urlencode(query_params)
response["HX-Push"] = reverse("home") + "?" + url_params response["HX-Push"] = reverse("home") + "?" + url_params
return response return response
else: else:
return HttpResponse("No results") return HttpResponse("No results")
class Drilldown(View):
template_name = "ui/drilldown/drilldown.html"
plan_name = "drilldown"
def get(self, request):
if request.user.is_anonymous:
sizes = settings.OPENSEARCH_MAIN_SIZES_ANON
else:
sizes = settings.OPENSEARCH_MAIN_SIZES
context = {}
if request.GET:
context = drilldown_search(request)
context["sizes"] = sizes
return render(request, self.template_name, context)
def post(self, request):
return drilldown_search(request)
class ThresholdInfoModal(APIView): class ThresholdInfoModal(APIView):
parser_classes = [FormParser] parser_classes = [FormParser]
plan_name = "drilldown" plan_name = "drilldown"