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

View File

@ -81,7 +81,7 @@
hx-post="{% url 'home' %}"
hx-trigger="keyup changed delay:200ms"
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">
<i class="fas fa-magnifying-glass"></i>
</span>
@ -122,7 +122,11 @@
<span class="select">
<select name="size">
{% for size in sizes %}
<option value="{{ size }}">{{ size }}</option>
{% if size == params.size %}
<option selected value="{{ size }}">{{ size }}</option>
{% else %}
<option value="{{ size }}">{{ size }}</option>
{% endif %}
{% endfor %}
</select>
<span class="icon is-small is-left">
@ -142,9 +146,26 @@
<div class="control has-icons-left">
<span class="select">
<select id="source" name="source">
<option selected value="all">All</option>
<option value="irc">IRC</option>
<option value="dis">Discord</option>
{% if params.source == 'irc' %}
<option selected value="irc">IRC</option>
{% else %}
<option value="irc">IRC</option>
{% endif %}
{% if params.source == 'dis' %}
<option selected value="dis">Discord</option>
{% else %}
<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>
<span class="icon is-small is-left">
<i class="fas fa-magnifying-glass"></i>
@ -162,8 +183,24 @@
<div id="sentiment">
<div class="field has-addons">
<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">
<output for="sliderWithValue" class="slider-output">0</output>
<input
{% 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>
</div>
<p class="control">
@ -174,25 +211,47 @@
</div>
<div class="control">
<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">
<i class="fa-solid fa-face-frown"></i>
</span>
</label>
<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">
<i class="fa-solid fa-face-smile"></i>
</span>
</label>
<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">
<i class="fa-solid fa-face-smile"></i>
</span>
</label>
<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">
<i class="fa-solid fa-face-meh-blank"></i>
</span>
@ -201,7 +260,11 @@
</div>
<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">
Check sentiment
</label>
@ -210,7 +273,7 @@
<div id="date">
<div class="field">
<div class="control">
<input type="date" name="dates">
<input type="date" name="dates" value="{{ params.date }}">
<script>
var options = {
"type": "datetime",
@ -218,6 +281,10 @@
"color": "info",
"validateLabel": "Save",
"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
var calendars = bulmaCalendar.attach('[type="date"]', options);
@ -234,19 +301,31 @@
</div>
<div class="control">
<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">
<i class="fa-solid fa-sort-down"></i>
</span>
</label>
<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">
<i class="fa-solid fa-sort-up"></i>
</span>
</label>
<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">
<i class="fa-solid fa-sort"></i>
</span>
@ -264,6 +343,9 @@
<script>
var inputTags = document.getElementById('tags');
new BulmaTagsInput(inputTags);
{% for tag in tags %}
inputTags.BulmaTagsInput().add("{{ tag|escapejs }}");
{% endfor %}
inputTags.BulmaTagsInput().on('before.add', function(item) {
if (item.includes(": ")) {
var spl = item.split(": ");
@ -293,6 +375,9 @@
</div>
<div class="block">
<div id="results">
{% if results %}
{% include 'ui/drilldown/results.html' %}
{% endif %}
</div>
</div>
<div id="modals-here">

View File

@ -46,6 +46,96 @@ class DrilldownTableView(View, SingleTableMixin):
return HttpResponse("No results")
def parse_dates(dates):
spl = dates.split(" - ")
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")
return {
"from_date": from_date,
"to_date": to_date,
"from_time": from_time,
"to_time": to_time,
}
else:
message = "Invalid dates"
message_class = "danger"
return {"message": message, "class": message_class}
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"
if request.GET:
query_params = request.GET.dict()
elif request.POST:
query_params = request.POST.dict()
# Parse the dates
if "dates" in query_params:
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:
return render(request, template_name, context)
context["data"] = json.dumps(
[
{
"text": item.get("msg", None) or item.get("id"),
"nick": item.get("nick", None),
"value": item.get("sentiment", None) or None,
"date": item.get("ts"),
}
for item in context["results"]
]
)
if 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
return response
else:
return HttpResponse("No results")
class Drilldown(View):
template_name = "ui/drilldown/drilldown.html"
plan_name = "drilldown"
@ -55,41 +145,15 @@ class Drilldown(View):
sizes = settings.OPENSEARCH_MAIN_SIZES_ANON
else:
sizes = settings.OPENSEARCH_MAIN_SIZES
context = {
"sizes": sizes,
}
context = {}
if request.GET:
context = drilldown_search(request)
context["sizes"] = sizes
return render(request, self.template_name, context)
def post(self, request):
template_name = "ui/drilldown/results.html"
data_args = request.POST.dict()
del data_args["csrfmiddlewaretoken"]
print("rep", repr(data_args["dates"]))
if data_args["dates"] == " - ":
del data_args["dates"]
url_params = urllib.parse.urlencode(data_args)
print("url_params", url_params)
context = query_results(request)
if "message" in context:
return render(request, self.template_name, context)
context["data"] = json.dumps(
[
{
"text": item.get("msg", None) or item.get("id"),
"nick": item.get("nick", None),
"value": item.get("sentiment", None) or None,
"date": item.get("ts"),
}
for item in context["results"]
]
)
if context:
response = render(request, template_name, context)
response["HX-Push"] = reverse("home") + "?" + url_params
return response
else:
return HttpResponse("No results")
return drilldown_search(request)
class ThresholdInfoModal(APIView):