mirror of
https://github.com/ail-project/ail-framework.git
synced 2025-01-18 16:36:13 +00:00
chg: [chat viewer] add chat messages by current year heatmap
This commit is contained in:
parent
55a35bf3f4
commit
9a388dc9cb
4 changed files with 122 additions and 102 deletions
|
@ -795,6 +795,19 @@ def api_get_nb_week_messages(chat_type, chat_instance_uuid, chat_id):
|
||||||
week = chat.get_nb_week_messages()
|
week = chat.get_nb_week_messages()
|
||||||
return week, 200
|
return week, 200
|
||||||
|
|
||||||
|
def api_get_nb_year_messages(chat_type, chat_instance_uuid, chat_id, year):
|
||||||
|
chat = get_obj_chat(chat_type, chat_instance_uuid, chat_id)
|
||||||
|
if not chat.exists():
|
||||||
|
return {"status": "error", "reason": "Unknown chat"}, 404
|
||||||
|
try:
|
||||||
|
year = int(year)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
year = datetime.now().year
|
||||||
|
nb_max, nb = chat.get_nb_year_messages(year)
|
||||||
|
nb = [[date, value] for date, value in nb.items()]
|
||||||
|
return {'max': nb_max, 'nb': nb}, 200
|
||||||
|
|
||||||
|
|
||||||
def api_get_chat_participants(chat_type, chat_subtype, chat_id):
|
def api_get_chat_participants(chat_type, chat_subtype, chat_id):
|
||||||
if chat_type not in ['chat', 'chat-subchannel', 'chat-thread']:
|
if chat_type not in ['chat', 'chat-subchannel', 'chat-thread']:
|
||||||
return {"status": "error", "reason": "Unknown chat type"}, 400
|
return {"status": "error", "reason": "Unknown chat type"}, 400
|
||||||
|
|
|
@ -11,7 +11,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
# from flask import url_for
|
# from flask import url_for
|
||||||
|
|
||||||
sys.path.append(os.environ['AIL_BIN'])
|
sys.path.append(os.environ['AIL_BIN'])
|
||||||
|
@ -160,10 +160,14 @@ class AbstractChatObject(AbstractSubtypeObject, ABC):
|
||||||
return messages, {'nb': nb, 'page': page, 'nb_pages': nb_pages, 'total': total, 'nb_first': nb_first, 'nb_last': nb_last}
|
return messages, {'nb': nb, 'page': page, 'nb_pages': nb_pages, 'total': total, 'nb_first': nb_first, 'nb_last': nb_last}
|
||||||
|
|
||||||
def get_timestamp_first_message(self):
|
def get_timestamp_first_message(self):
|
||||||
return r_object.zrange(f'messages:{self.type}:{self.subtype}:{self.id}', 0, 0, withscores=True)
|
first = r_object.zrange(f'messages:{self.type}:{self.subtype}:{self.id}', 0, 0, withscores=True)
|
||||||
|
if first:
|
||||||
|
return int(first[0][1])
|
||||||
|
|
||||||
def get_timestamp_last_message(self):
|
def get_timestamp_last_message(self):
|
||||||
return r_object.zrevrange(f'messages:{self.type}:{self.subtype}:{self.id}', 0, 0, withscores=True)
|
last = r_object.zrevrange(f'messages:{self.type}:{self.subtype}:{self.id}', 0, 0, withscores=True)
|
||||||
|
if last:
|
||||||
|
return int(last[0][1])
|
||||||
|
|
||||||
def get_first_message(self):
|
def get_first_message(self):
|
||||||
return r_object.zrange(f'messages:{self.type}:{self.subtype}:{self.id}', 0, 0)
|
return r_object.zrange(f'messages:{self.type}:{self.subtype}:{self.id}', 0, 0)
|
||||||
|
@ -223,6 +227,38 @@ class AbstractChatObject(AbstractSubtypeObject, ABC):
|
||||||
nb_day += 1
|
nb_day += 1
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
def get_message_years(self):
|
||||||
|
timestamp = datetime.utcfromtimestamp(float(self.get_timestamp_first_message()))
|
||||||
|
year_start = int(timestamp.strftime('%Y'))
|
||||||
|
timestamp = datetime.utcfromtimestamp(float(self.get_timestamp_last_message()))
|
||||||
|
year_end = int(timestamp.strftime('%Y'))
|
||||||
|
return list(range(year_start, year_end + 1))
|
||||||
|
|
||||||
|
def get_nb_year_messages(self, year):
|
||||||
|
nb_year = {}
|
||||||
|
nb_max = 0
|
||||||
|
start = int(datetime(year, 1, 1, 0, 0, 0, tzinfo=timezone.utc).timestamp())
|
||||||
|
end = int(datetime(year, 12, 31, 23, 59, 59, tzinfo=timezone.utc).timestamp())
|
||||||
|
|
||||||
|
for mess_t in r_object.zrangebyscore(f'messages:{self.type}:{self.subtype}:{self.id}', start, end, withscores=True):
|
||||||
|
timestamp = datetime.utcfromtimestamp(float(mess_t[1]))
|
||||||
|
date = timestamp.strftime('%Y-%m-%d')
|
||||||
|
if date not in nb_year:
|
||||||
|
nb_year[date] = 0
|
||||||
|
nb_year[date] += 1
|
||||||
|
nb_max = max(nb_max, nb_year[date])
|
||||||
|
|
||||||
|
subchannels = self.get_subchannels()
|
||||||
|
for gid in subchannels:
|
||||||
|
for mess_t in r_object.zrangebyscore(f'messages:{gid}', start, end, withscores=True):
|
||||||
|
timestamp = datetime.utcfromtimestamp(float(mess_t[1]))
|
||||||
|
date = timestamp.strftime('%Y-%m-%d')
|
||||||
|
if date not in nb_year:
|
||||||
|
nb_year[date] = 0
|
||||||
|
nb_year[date] += 1
|
||||||
|
|
||||||
|
return nb_max, nb_year
|
||||||
|
|
||||||
def get_message_meta(self, message, timestamp=None, translation_target='', options=None): # TODO handle file message
|
def get_message_meta(self, message, timestamp=None, translation_target='', options=None): # TODO handle file message
|
||||||
message = Messages.Message(message[9:])
|
message = Messages.Message(message[9:])
|
||||||
if not options:
|
if not options:
|
||||||
|
|
|
@ -76,6 +76,12 @@ def chats_explorer_instance():
|
||||||
chat_instance = chat_instance[0]
|
chat_instance = chat_instance[0]
|
||||||
return render_template('chat_instance.html', chat_instance=chat_instance)
|
return render_template('chat_instance.html', chat_instance=chat_instance)
|
||||||
|
|
||||||
|
@chats_explorer.route("chats/explorer/chats/selector", methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
@login_read_only
|
||||||
|
def chats_explorer_chats_selector():
|
||||||
|
return jsonify(chats_viewer.api_get_chats_selector())
|
||||||
|
|
||||||
@chats_explorer.route("chats/explorer/chat", methods=['GET'])
|
@chats_explorer.route("chats/explorer/chat", methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@login_read_only
|
@login_read_only
|
||||||
|
@ -123,6 +129,20 @@ def chats_explorer_messages_stats_week_all():
|
||||||
else:
|
else:
|
||||||
return jsonify(week[0])
|
return jsonify(week[0])
|
||||||
|
|
||||||
|
@chats_explorer.route("chats/explorer/messages/stats/year", methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
@login_read_only
|
||||||
|
def chats_explorer_messages_stats_year():
|
||||||
|
chat_type = request.args.get('type')
|
||||||
|
instance_uuid = request.args.get('subtype')
|
||||||
|
chat_id = request.args.get('id')
|
||||||
|
year = request.args.get('year')
|
||||||
|
stats = chats_viewer.api_get_nb_year_messages(chat_type, instance_uuid, chat_id, year)
|
||||||
|
if stats[1] != 200:
|
||||||
|
return create_json_response(stats[0], stats[1])
|
||||||
|
else:
|
||||||
|
return jsonify(stats[0])
|
||||||
|
|
||||||
@chats_explorer.route("/chats/explorer/subchannel", methods=['GET'])
|
@chats_explorer.route("/chats/explorer/subchannel", methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@login_read_only
|
@login_read_only
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Chats Protocols - AIL</title>
|
<title>Chat - AIL</title>
|
||||||
<link rel="icon" href="{{ url_for('static', filename='image/ail-icon.png') }}">
|
<link rel="icon" href="{{ url_for('static', filename='image/ail-icon.png') }}">
|
||||||
|
|
||||||
<!-- Core CSS -->
|
<!-- Core CSS -->
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/d3.min.js')}}"></script>
|
<script src="{{ url_for('static', filename='js/d3.min.js')}}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/d3/heatmap_week_hour.js')}}"></script>
|
<script src="{{ url_for('static', filename='js/d3/heatmap_week_hour.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/echarts.min.js')}}"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chat-message-left,
|
.chat-message-left,
|
||||||
|
@ -112,6 +113,9 @@
|
||||||
<div id="heatmapweekhour"></div>
|
<div id="heatmapweekhour"></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<h5>Messages by year:</h5>
|
||||||
|
<div id="heatmapyear" style="width: 100%;height: 300px;"></div>
|
||||||
|
|
||||||
{% with translate_url=url_for('chats_explorer.chats_explorer_chat', subtype=chat['subtype']), obj_id=chat['id'], pagination=chat['pagination'] %}
|
{% with translate_url=url_for('chats_explorer.chats_explorer_chat', subtype=chat['subtype']), obj_id=chat['id'], pagination=chat['pagination'] %}
|
||||||
{% include 'chats_explorer/block_translation.html' %}
|
{% include 'chats_explorer/block_translation.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -218,109 +222,56 @@ d3.json("{{ url_for('chats_explorer.chats_explorer_messages_stats_week') }}?type
|
||||||
})
|
})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// set the dimensions and margins of the graph
|
var heatyearChart = echarts.init(document.getElementById('heatmapyear'));
|
||||||
const margin = {top: 30, right: 30, bottom: 30, left: 30},
|
window.addEventListener('resize', function() {
|
||||||
width = 450 - margin.left - margin.right,
|
heatyearChart.resize();
|
||||||
height = 450 - margin.top - margin.bottom;
|
});
|
||||||
|
var optionheatmap;
|
||||||
|
|
||||||
// append the svg object to the body of the page
|
optionheatmap = {
|
||||||
const svg = d3.select("#my_dataviz")
|
tooltip: {
|
||||||
.append("svg")
|
position: 'top',
|
||||||
.attr("width", width + margin.left + margin.right)
|
formatter: function (p) {
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
//const format = echarts.time.format(p.data[0], '{yyyy}-{MM}-{dd}', false);
|
||||||
.append("g")
|
return p.data[0] + ': ' + p.data[1];
|
||||||
.attr("transform", `translate(${margin.left},${margin.top})`);
|
|
||||||
|
|
||||||
// Labels of row and columns
|
|
||||||
const myGroups = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
|
|
||||||
const myVars = ["v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10"]
|
|
||||||
|
|
||||||
//Read the data
|
|
||||||
d3.csv("").then( function(data) {
|
|
||||||
|
|
||||||
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
|
|
||||||
const myGroups = Array.from(new Set(data.map(d => d.group)))
|
|
||||||
const myVars = Array.from(new Set(data.map(d => d.variable)))
|
|
||||||
|
|
||||||
// Build X scales and axis:
|
|
||||||
const x = d3.scaleBand()
|
|
||||||
.range([ 0, width ])
|
|
||||||
.domain(myGroups)
|
|
||||||
.padding(0.05);
|
|
||||||
svg.append("g")
|
|
||||||
.style("font-size", 15)
|
|
||||||
.attr("transform", `translate(0, ${height})`)
|
|
||||||
.call(d3.axisBottom(x).tickSize(0))
|
|
||||||
.select(".domain").remove()
|
|
||||||
|
|
||||||
// Build Y scales and axis:
|
|
||||||
const y = d3.scaleBand()
|
|
||||||
.range([ height, 0 ])
|
|
||||||
.domain(myVars)
|
|
||||||
.padding(0.01);
|
|
||||||
svg.append("g")
|
|
||||||
.style("font-size", 15)
|
|
||||||
.call(d3.axisLeft(y).tickSize(0))
|
|
||||||
.select(".domain").remove()
|
|
||||||
|
|
||||||
// Build color scale
|
|
||||||
const myColor = d3.scaleSequential()
|
|
||||||
.interpolator(d3.interpolateInferno)
|
|
||||||
.domain([1,100])
|
|
||||||
|
|
||||||
// create a tooltip
|
|
||||||
const tooltip = d3.select("#my_dataviz")
|
|
||||||
.append("div")
|
|
||||||
.style("opacity", 0)
|
|
||||||
.attr("class", "tooltip")
|
|
||||||
.style("background-color", "white")
|
|
||||||
.style("border", "solid")
|
|
||||||
.style("border-width", "2px")
|
|
||||||
.style("border-radius", "5px")
|
|
||||||
.style("padding", "5px")
|
|
||||||
|
|
||||||
// Three function that change the tooltip when user hover / move / leave a cell
|
|
||||||
const mouseover = function(event,d) {
|
|
||||||
tooltip.style("opacity", 1)
|
|
||||||
d3.select(this)
|
|
||||||
.style("stroke", "black")
|
|
||||||
.style("opacity", 1)
|
|
||||||
}
|
}
|
||||||
const mousemove = function(event,d) {
|
},
|
||||||
tooltip.html("The exact value of<br>this cell is: " + d)
|
visualMap: {
|
||||||
.style("left", (event.x)/2 + "px")
|
min: 0,
|
||||||
.style("top", (event.y)/2 + "px")
|
max: 100,
|
||||||
}
|
calculable: true,
|
||||||
const mouseleave = function(d) {
|
orient: 'horizontal',
|
||||||
tooltip.style("opacity", 0)
|
left: '500',
|
||||||
d3.select(this)
|
top: '-10'
|
||||||
.style("stroke", "none")
|
},
|
||||||
.style("opacity", 0.8)
|
calendar: [
|
||||||
|
{
|
||||||
|
orient: 'horizontal',
|
||||||
|
//range: new Date().getFullYear(),
|
||||||
|
range: '2024',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'heatmap',
|
||||||
|
coordinateSystem: 'calendar',
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
};
|
||||||
|
heatyearChart.setOption(optionheatmap);
|
||||||
|
|
||||||
|
$.getJSON("{{ url_for('chats_explorer.chats_explorer_messages_stats_year') }}?type=chat&subtype={{ chat['subtype'] }}&id={{ chat['id'] }}")
|
||||||
|
.done(function(data) {
|
||||||
|
optionheatmap['visualMap']['max'] = data['max']
|
||||||
|
optionheatmap['series'][0]['data'] = data['nb']
|
||||||
|
heatyearChart.setOption(optionheatmap)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
svg.selectAll()
|
|
||||||
.data(data, function(d) {return d.group+':'+d.variable;})
|
|
||||||
.join("rect")
|
|
||||||
.attr("x", function(d) { return x(d.group) })
|
|
||||||
.attr("y", function(d) { return y(d.variable) })
|
|
||||||
.attr("rx", 4)
|
|
||||||
.attr("ry", 4)
|
|
||||||
.attr("width", x.bandwidth() )
|
|
||||||
.attr("height", y.bandwidth() )
|
|
||||||
.style("fill", function(d) { return myColor(d.value)} )
|
|
||||||
.style("stroke-width", 4)
|
|
||||||
.style("stroke", "none")
|
|
||||||
.style("opacity", 0.8)
|
|
||||||
.on("mouseover", mouseover)
|
|
||||||
.on("mousemove", mousemove)
|
|
||||||
.on("mouseleave", mouseleave)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
*/
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue