Implement graphs properly

This commit is contained in:
Mark Veidemanis 2022-07-21 13:50:51 +01:00
parent d40557bbba
commit 4b4b8bfe72
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
13 changed files with 400 additions and 220 deletions

View File

@ -22,7 +22,7 @@ from django.views.generic import TemplateView
from core.ui.views.drilldown import Drilldown
from core.views import Billing, Cancel, Home, Order, Portal, Signup
from core.views.callbacks import Callback
from core.views.charts import SentimentChartJSONView, VolumeChartJSONView
from core.views.dynamic.search import APISearch, Search
urlpatterns = [
path("", Home.as_view(), name="home"),
@ -43,14 +43,6 @@ urlpatterns = [
path("accounts/", include("django.contrib.auth.urls")),
path("accounts/signup/", Signup.as_view(), name="signup"),
path("ui/drilldown/", Drilldown.as_view(), name="drilldown"),
path(
"ui/drilldown/chart/volume/json/",
VolumeChartJSONView.as_view(),
name="chart_volume_json",
),
path(
"ui/drilldown/chart/sentiment/json/",
SentimentChartJSONView.as_view(),
name="chart_sentiment_json",
),
path("parts/search/", Search.as_view(), name="search"),
path("api/search/", APISearch.as_view(), name="api_search"),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

34
core/static/chart.js Normal file
View File

@ -0,0 +1,34 @@
function loadJson(selector) {
return JSON.parse(document.querySelector(selector).getAttribute('data-json'));
}
var jsonData = loadJson('#jsonData');
var data = jsonData.map((item) => item.value);
var labels = jsonData.map((item) => item.date);
var config = {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Sentiment',
backgroundColor: 'black',
borderColor: 'lightblue',
data: data,
fill: false
}
]
},
options: {
responsive: true,
}
}
var element = document.getElementById('volume');
element.removeAttribute("height");
element.removeAttribute("width");
var ctx = document.getElementById('volume').getContext('2d');
new Chart(ctx, config);

View File

@ -10,6 +10,7 @@
<link rel="shortcut icon" href="{% static 'favicon.ico' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<link rel="stylesheet" href="https://site-assets.fontawesome.com/releases/v6.1.1/css/all.css" />
<script src="https://unpkg.com/htmx.org@1.8.0" integrity="sha384-cZuAZ+ZbwkNRnrKi05G/fjBX+azI9DNOkNYysZ0I/X5ZFgsmMiBXgDZof30F5ofc" crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {

View File

@ -0,0 +1,182 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="box">
<form method="POST">
{% csrf_token %}
<div class="field">
<label class="label">Search</label>
<div class="field-body">
<div class="field">
<div class="control is-expanded has-icons-left">
<input name="query" class="input" type="text" placeholder="Query">
<span class="icon is-small is-left">
<i class="fas fa-magnifying-glass"></i>
</span>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<label class="label">Timescale</label>
<div class="field-body">
<div class="field">
<div class="control is-expanded has-icons-left">
<div class="select is-fullwidth">
<select name="timescale">
{% for timescale in timescales %}
<option value="{{ timescale }}">{{ timescale }}</option>
{% endfor %}
</select>
<span class="icon is-small is-left">
<i class="fas fa-magnifying-glass"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="column">
<label class="label">Fields</label>
<div class="field">
<div class="control is-expanded has-icons-left">
<div class="select is-fullwidth is-multiple">
<select multiple name="fields">
{% for field in fields %}
<option value="{{ field }}">{{ field }}</option>
{% endfor %}
</select>
<span class="icon is-small is-left">
<i class="fas fa-magnifying-glass"></i>
</span>
</div>
</div>
</div>
</div>
<div class="column">
<label class="label">Results</label>
<div class="field">
<div class="control is-expanded has-icons-left">
<div class="select is-fullwidth">
<select name="size">
{% for size in sizes %}
<option value="{{ size }}">{{ size }}</option>
{% endfor %}
</select>
<span class="icon is-small is-left">
<i class="fas fa-magnifying-glass"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-primary is-fullwidth">
Search
</button>
<button hx-post="{% url 'search' %}"
hx-trigger="click"
hx-target="#parent-div"
hx-swap="outerHTML">
Click Me!
</button>
</div>
</div>
</form>
</div>
{% if results is not None %}
<div class="box">
<div class="table-container">
<table class="table is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th>TS</th>
<th>msg</th>
<th>host</th>
<th>nick</th>
<th>channel</th>
<th>net</th>
</tr>
</thead>
<tbody>
{% for item in results %}
<tr>
<td>{{ item.ts }}</td>
<td>{{ item.msg }}</td>
<td>{{ item.host }}</td>
<td>{{ item.nick }}</td>
<td>{{ item.channel }}</td>
<td>{{ item.net }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="box">
<div class="columns">
<div class="column">
<p>{{ card }} hits</p>
</div>
{% if redacted != 0 %}
<div class="column">
<p>{{ redacted }} redacted</p>
</div>
{% endif %}
{% if exemption is not None %}
<div class="column">
<p>god mode</p>
</div>
{% endif %}
<div class="column">
<p>{{ took }}ms</p>
</div>
</div>
</div>
<div class="columns">
<div class="column is-half">
<div class="box">
<label class="label">Volume</label>
<canvas id="volumeChart"></canvas>
<script type="text/javascript">
$.post('{% url "chart_volume_json" %}', function(data) {
var ctx = $("#volumeChart").get(0).getContext("2d");
new Chart(ctx, {
type: 'line', data: data
});
});
</script>
</div>
</div>
<div class="column">
<div class="box">
<label class="label">Sentiment</label>
<canvas id="sentimentChart"></canvas>
<script type="text/javascript">
$.post('{% url "chart_sentiment_json" %}', function(data) {
var ctx = $("#sentimentChart").get(0).getContext("2d");
new Chart(ctx, {
type: 'line', data: data
});
});
</script>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% load static %}
<html>
<head>
</head>
<body>
</body>
</html>

View File

@ -1,8 +1,7 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.10.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
<div class="box">
<form method="POST">
{% csrf_token %}
@ -79,102 +78,16 @@
<div class="field">
<div class="control">
<button class="button is-primary is-fullwidth">
<button class="button is-primary is-fullwidth" hx-post="{% url 'search' %}" hx-trigger="click" hx-target="#results" hx-swap="outerHTML">
Search
</button>
</div>
</div>
</form>
</div>
{% if results is not None %}
<div class="box">
<div class="table-container">
<table class="table is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th>TS</th>
<th>msg</th>
<th>host</th>
<th>nick</th>
<th>channel</th>
<th>net</th>
</tr>
</thead>
<tbody>
{% for item in results %}
<tr>
<td>{{ item.ts }}</td>
<td>{{ item.msg }}</td>
<td>{{ item.host }}</td>
<td>{{ item.nick }}</td>
<td>{{ item.channel }}</td>
<td>{{ item.net }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="box">
<div class="columns">
<div class="column">
<p>{{ card }} hits</p>
</div>
{% if redacted != 0 %}
<div class="column">
<p>{{ redacted }} redacted</p>
</div>
{% endif %}
{% if exemption is not None %}
<div class="column">
<p>god mode</p>
</div>
{% endif %}
<div class="column">
<p>{{ took }}ms</p>
</div>
</div>
</div>
<div class="columns">
<div class="column is-half">
<div class="box">
<label class="label">Volume</label>
<canvas id="volumeChart"></canvas>
<script type="text/javascript">
$.post('{% url "chart_volume_json" %}', { name: "John", time: "2pm" }, function(data) {
var ctx = $("#volumeChart").get(0).getContext("2d");
new Chart(ctx, {
type: 'line', data: data
});
});
</script>
</div>
</div>
<div class="column">
<div class="box">
<label class="label">Sentiment</label>
<canvas id="sentimentChart"></canvas>
<script type="text/javascript">
$.post('{% url "chart_sentiment_json" %}', { name: "John", time: "2pm" }, function(data) {
var ctx = $("#sentimentChart").get(0).getContext("2d");
new Chart(ctx, {
type: 'line', data: data
});
});
</script>
</div>
</div>
</div>
{% endif %}
<div id="results">
</div>
{% endblock %}

View File

@ -0,0 +1,66 @@
{% load static %}
<div id="results">
{% if results is not None %}
<div style="display: none" id="jsonData" data-json="{{ data }}"></div>
<div class="columns">
<div class="column">
<div class="box">
<label class="label">Sentiment</label>
<canvas id="volume"></canvas>
<script src="{% static 'chart.js' %}"></script>
</div>
</div>
</div>
<div class="box">
<div class="table-container">
<table class="table is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th>TS</th>
<th>msg</th>
<th>host</th>
<th>nick</th>
<th>channel</th>
<th>net</th>
</tr>
</thead>
<tbody>
{% for item in results %}
<tr>
<td>{{ item.ts }}</td>
<td>{{ item.msg }}</td>
<td>{{ item.host }}</td>
<td>{{ item.nick }}</td>
<td>{{ item.channel }}</td>
<td>{{ item.net }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="box">
<div class="columns">
<div class="column">
<p>{{ card }} hits</p>
</div>
{% if redacted != 0 %}
<div class="column">
<p>{{ redacted }} redacted</p>
</div>
{% endif %}
{% if exemption is not None %}
<div class="column">
<p>god mode</p>
</div>
{% endif %}
<div class="column">
<p>{{ took }}ms</p>
</div>
</div>
</div>
{% endif %}
</div>

View File

@ -1,14 +1,11 @@
import pprint
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
from django.views import View
from core.lib.opensearch import initialise_opensearch, run_main_query
from core.lib.opensearch import initialise_opensearch
client = initialise_opensearch()
pp = pprint.PrettyPrinter(indent=4)
class Drilldown(LoginRequiredMixin, View):
@ -24,36 +21,3 @@ class Drilldown(LoginRequiredMixin, View):
"timescales": settings.OPENSEARCH_MAIN_TIMESCALES,
}
return render(request, self.template_name, context)
def post(self, request):
if not request.user.has_plan(self.plan_name):
return render(request, "denied.html")
fields = None
if "fields" in request.POST:
fields = request.POST.getlist("fields")
if "size" in request.POST:
size = request.POST["size"]
if "query" in request.POST:
query = request.POST["query"]
results = run_main_query(client, request.user, query, fields, size)
if not results:
return render(request, "denied.html")
# pp.pprint(results)
results_parsed = []
if "hits" in results.keys():
if "hits" in results["hits"]:
for item in results["hits"]["hits"]:
results_parsed.append(item["_source"])
context = {
"query": query,
"results": results_parsed,
"card": results["hits"]["total"]["value"],
"took": results["took"],
"redacted": results["redacted"],
"exemption": results["exemption"],
"fields": settings.OPENSEARCH_MAIN_SEARCH_FIELDS,
"sizes": settings.OPENSEARCH_MAIN_SIZES,
"timescales": settings.OPENSEARCH_MAIN_TIMESCALES,
}
return render(request, self.template_name, context)
return render(request, self.template_name)

View File

@ -41,14 +41,17 @@ class Order(LoginRequiredMixin, View):
def get(self, request, plan_name):
plan = Plan.objects.get(name=plan_name)
try:
session = stripe.checkout.Session.create(
payment_method_types=settings.ALLOWED_PAYMENT_METHODS,
mode="subscription",
customer=request.user.stripe_id,
line_items=assemble_plan_map(product_id_filter=plan.product_id),
success_url=request.build_absolute_uri(reverse("success")),
cancel_url=request.build_absolute_uri(reverse("cancel")),
)
cast = {
"payment_method_types": settings.ALLOWED_PAYMENT_METHODS,
"mode": "subscription",
"customer": request.user.stripe_id,
"line_items": assemble_plan_map(product_id_filter=plan.product_id),
"success_url": request.build_absolute_uri(reverse("success")),
"cancel_url": request.build_absolute_uri(reverse("cancel")),
}
if request.user.is_superuser:
cast["discounts"] = [{"coupon": settings.STRIPE_ADMIN_COUPON}]
session = stripe.checkout.Session.create(**cast)
Session.objects.create(user=request.user, session=session.id)
return redirect(session.url)
# return JsonResponse({'id': session.id})

View File

@ -1,71 +0,0 @@
from chartjs.views.lines import BaseLineChartView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
class CSRFExemptMixin(object):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(CSRFExemptMixin, self).dispatch(*args, **kwargs)
class VolumeChartJSONView(CSRFExemptMixin, LoginRequiredMixin, BaseLineChartView):
def post(self, request, *args, **kwargs):
print("POST")
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
data = super(VolumeChartJSONView, self).get_context_data(**kwargs)
# data["colors"] = islice(next_color(), 0, 50)
print("KWARGS", kwargs)
return data
def get_labels(self):
"""Return 7 labels for the x-axis."""
return ["January", "February", "March", "April", "May", "June", "July"]
def get_providers(self):
"""Return names of datasets."""
return ["Central", "Eastside", "Westside"]
def get_data(self):
"""Return 3 datasets to plot."""
return [
[75, 44, 92, 11, 44, 95, 35],
[41, 92, 18, 3, 73, 87, 92],
[87, 21, 94, 3, 90, 13, 65],
]
class SentimentChartJSONView(CSRFExemptMixin, LoginRequiredMixin, BaseLineChartView):
def post(self, request, *args, **kwargs):
print("POST")
print(request.POST)
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
data = super(SentimentChartJSONView, self).get_context_data(**kwargs)
# data["colors"] = islice(next_color(), 0, 50)
print("KWARGS", kwargs)
return data
def get_labels(self):
"""Return 7 labels for the x-axis."""
return ["January", "February", "March", "April", "May", "June", "July"]
def get_providers(self):
"""Return names of datasets."""
return ["Central", "Eastside", "Westside"]
def get_data(self):
"""Return 3 datasets to plot."""
return [
[75, 44, 92, 11, 44, 95, 35],
[41, 92, 18, 3, 73, 87, 92],
[87, 21, 94, 3, 90, 13, 65],
]

View File

View File

@ -0,0 +1,86 @@
from rest_framework.views import APIView
import logging
from django.conf import settings
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from django.contrib.auth.mixins import LoginRequiredMixin
import json
from django.views.decorators.csrf import csrf_exempt
from django.views import View
from rest_framework.parsers import JSONParser
from core.lib.opensearch import initialise_opensearch, run_main_query
client = initialise_opensearch()
def query_results(request, post_params, api=False):
fields = None
if "fields" in request.POST:
fields = request.POST.getlist("fields")
if "size" in request.POST:
size = request.POST["size"]
if "query" in request.POST:
query = request.POST["query"]
results = run_main_query(client, request.user, query, fields, size)
if not results:
return False
# pp.pprint(results)
results_parsed = []
if "hits" in results.keys():
if "hits" in results["hits"]:
for item in results["hits"]["hits"]:
results_parsed.append(item["_source"])
context = {
"query": query,
"results": results_parsed,
"card": results["hits"]["total"]["value"],
"took": results["took"],
"redacted": results["redacted"],
"exemption": results["exemption"],
"fields": settings.OPENSEARCH_MAIN_SEARCH_FIELDS,
"sizes": settings.OPENSEARCH_MAIN_SIZES,
"timescales": settings.OPENSEARCH_MAIN_TIMESCALES,
}
return context
class Search(LoginRequiredMixin, View):
#parser_classes = [JSONParser]
template_name = "ui/results.html"
plan_name = "drilldown"
def post(self, request):
if not request.user.has_plan(self.plan_name):
return render(request, "denied.html")
context = query_results(request, request.POST)
print("context: ", context)
context['data'] = json.dumps(
[
{
'id': item.get('id'),
'value': item.get("sentiment", 0),
'date': item.get("ts"),
}
for item in context["results"]
]
)
print("context['data']: ", context['data'])
if context:
print("OGING TO RENDER")
return render(request, self.template_name, context)
else:
return HttpResponse("No results")
class APISearch(LoginRequiredMixin, View):
#parser_classes = [JSONParser]
template_name = "ui/results.html"
plan_name = "drilldown"
def post(self, request):
print("POST")
if not request.user.has_plan(self.plan_name):
return JsonResponse({"success": False})
print("PERMS")
context = query_results(request, request.POST)
print("CONTEXT", context)
return JsonResponse(context)

View File

@ -5,4 +5,3 @@ crispy-bulma
opensearch-py
stripe
django-rest-framework
django-chartjs