mirror of https://github.com/CIRCL/AIL-framework
chg: [user-account] chat user-accounts: show usernames list + usernames timeline
parent
ced00d14cb
commit
c7204d5bbd
|
@ -465,6 +465,22 @@ def get_user_account_nb_all_week_messages(user_id, chats, subchannels):
|
|||
nb_day += 1
|
||||
return stats
|
||||
|
||||
def get_user_account_usernames_timeline(subtype, user_id):
|
||||
user_account = UsersAccount.UserAccount(user_id, subtype)
|
||||
usernames = user_account.get_usernames_history()
|
||||
if usernames:
|
||||
for row in usernames:
|
||||
row['obj'] = row['obj'].rsplit(':', 1)[1]
|
||||
if row['start'] > row['end']:
|
||||
t = row['start']
|
||||
row['start'] = row['end']
|
||||
row['end'] = t
|
||||
if row['start'] == row['end']:
|
||||
row['end'] = row['end'] + 1
|
||||
row['start'] = row['start'] * 1000
|
||||
row['end'] = row['end'] * 1000
|
||||
return usernames
|
||||
|
||||
def get_user_account_chats_chord(subtype, user_id):
|
||||
nb = {}
|
||||
user_account = UsersAccount.UserAccount(user_id, subtype)
|
||||
|
@ -733,7 +749,7 @@ def api_get_user_account(user_id, instance_uuid, translation_target=None):
|
|||
user_account = UsersAccount.UserAccount(user_id, instance_uuid)
|
||||
if not user_account.exists():
|
||||
return {"status": "error", "reason": "Unknown user-account"}, 404
|
||||
meta = user_account.get_meta({'chats', 'icon', 'info', 'subchannels', 'threads', 'translation', 'username', 'username_meta'}, translation_target=translation_target)
|
||||
meta = user_account.get_meta({'chats', 'icon', 'info', 'subchannels', 'threads', 'translation', 'username', 'usernames', 'username_meta'}, translation_target=translation_target)
|
||||
if meta['chats']:
|
||||
meta['chats'] = get_user_account_chats_meta(user_id, meta['chats'], meta['subchannels'])
|
||||
return meta, 200
|
||||
|
|
|
@ -132,7 +132,15 @@ class UserAccount(AbstractSubtypeObject):
|
|||
return self._get_timeline_username().get_last_obj_id()
|
||||
|
||||
def get_usernames(self):
|
||||
return self._get_timeline_username().get_objs_ids()
|
||||
usernames = []
|
||||
names = self._get_timeline_username().get_objs_ids()
|
||||
for name in names:
|
||||
_, subtype, obj_id = name.split(':', 2)
|
||||
usernames.append({'type': 'username', 'subtype': subtype, 'id': obj_id})
|
||||
return usernames
|
||||
|
||||
def get_usernames_history(self):
|
||||
return self._get_timeline_username().get_objs()
|
||||
|
||||
def update_username_timeline(self, username_global_id, timestamp):
|
||||
self._get_timeline_username().add_timestamp(timestamp, username_global_id)
|
||||
|
|
|
@ -112,9 +112,29 @@ class Timeline:
|
|||
for block in r_meta.zrange(f'line:{self.id}:{self.name}', 0, -1):
|
||||
if block:
|
||||
if block.startswith('start:'):
|
||||
print(self._get_block_obj_global_id(block[6:]))
|
||||
objs.add(self._get_block_obj_global_id(block[6:]))
|
||||
return objs
|
||||
|
||||
def get_objs(self):
|
||||
objs = []
|
||||
blocks = r_meta.zrange(f'line:{self.id}:{self.name}', 0, -1, withscores=True)
|
||||
for i in range(0, len(blocks), 2):
|
||||
block1, score1 = blocks[i]
|
||||
block2, score2 =blocks[i + 1]
|
||||
score1 = int(score1)
|
||||
score2 = int(score2)
|
||||
if block1.startswith('start:'):
|
||||
start = score1
|
||||
end = score2
|
||||
obj = self._get_block_obj_global_id(block1[6:])
|
||||
else:
|
||||
start = score2
|
||||
end = score1
|
||||
obj = self._get_block_obj_global_id(block2[6:])
|
||||
objs.append({'obj': obj, 'start': start, 'end': end})
|
||||
return objs
|
||||
|
||||
# def get_objs_ids(self):
|
||||
# objs = {}
|
||||
# last_obj_id = None
|
||||
|
|
|
@ -288,6 +288,9 @@ def objects_user_account():
|
|||
if target == "Don't Translate":
|
||||
target = None
|
||||
user_account = chats_viewer.api_get_user_account(user_id, instance_uuid, translation_target=target)
|
||||
# print()
|
||||
# print(user_account[0]['usernames'])
|
||||
# print()
|
||||
if user_account[1] != 200:
|
||||
return create_json_response(user_account[0], user_account[1])
|
||||
else:
|
||||
|
@ -297,6 +300,15 @@ def objects_user_account():
|
|||
ail_tags=Tag.get_modal_add_tags(user_account['id'], user_account['type'], user_account['subtype']),
|
||||
translation_languages=languages, translation_target=target)
|
||||
|
||||
@chats_explorer.route("/objects/user-account_usernames_timeline_json", methods=['GET']) # TODO API
|
||||
@login_required
|
||||
@login_read_only
|
||||
def objects_user_account_usernames_timeline_json():
|
||||
subtype = request.args.get('subtype')
|
||||
user_id = request.args.get('id')
|
||||
json_graph = chats_viewer.get_user_account_usernames_timeline(subtype, user_id)
|
||||
return jsonify(json_graph)
|
||||
|
||||
@chats_explorer.route("/objects/user-account_chats_chord_json", methods=['GET']) # TODO API
|
||||
@login_required
|
||||
@login_read_only
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
const create_directed_chord_diagram = (container_id, data, min_value= 0, max_value = -1, fct_mouseover, fct_mouseout, options) => {
|
||||
|
||||
if(!Object.keys(data.data).length){
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter data by value between target and source
|
||||
if (min_value > 0) {
|
||||
data.data = data.data.filter(function(value) {
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
//Requirement: - D3v7
|
||||
// - jquery
|
||||
|
||||
// container_id = #container_id "data": [{"obj": username, "start": 1111111100000, "end": 2222222200000}, ...]
|
||||
// tooltip = d3 tooltip object
|
||||
|
||||
const create_timeline_basic = (container_id, data) => {
|
||||
|
||||
if(!Object.keys(data).length){
|
||||
return;
|
||||
}
|
||||
|
||||
const width = 800;
|
||||
const height = 100;
|
||||
const margin = { top: 10, right: 10, bottom: 40, left: 40 };
|
||||
|
||||
const colorScale = d3.scaleOrdinal(d3.schemeCategory10)
|
||||
.domain(data.map(d => d.obj));
|
||||
|
||||
const xScale = d3.scaleTime()
|
||||
.domain([d3.min(data, d => d.start), d3.max(data, d => d.end)])
|
||||
.range([margin.left, width - margin.right]);
|
||||
|
||||
const svg = d3.select(container_id)
|
||||
.append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
|
||||
const tooltip = d3.select("body")
|
||||
.append("div")
|
||||
.attr("class", "tooltip_basic_timeline")
|
||||
.style("opacity", 0)
|
||||
//d3.select(".tooltip")
|
||||
.style("position", "absolute")
|
||||
.style("text-align", "center")
|
||||
.style("padding", " 8px")
|
||||
.style("font", "sans-serif")
|
||||
.style("font-size", "12px")
|
||||
.style("background", "whitesmoke")
|
||||
.style("border", "0px")
|
||||
.style("border-radius", "8px")
|
||||
.style("pointer-events", "none");
|
||||
|
||||
// date-time format
|
||||
const dateTimeFormat = d3.timeFormat("%Y-%m-%d %H:%M");
|
||||
|
||||
svg.selectAll("rect")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", d => xScale(d.start))
|
||||
.attr("y", 20)
|
||||
.attr("width", d => xScale(d.end) - xScale(d.start))
|
||||
.attr("height", 20)
|
||||
.attr("fill", d => colorScale(d.obj))
|
||||
.on("mouseover", function(event, d) {
|
||||
tooltip.transition()
|
||||
.duration(200)
|
||||
.style("opacity", .9);
|
||||
tooltip.html(`${d.obj}<br>⏳ ${dateTimeFormat(new Date(d.start))}<br>⌛ ${dateTimeFormat(new Date(d.end))}`)
|
||||
.style("left", (event.pageX + 5) + "px")
|
||||
.style("top", (event.pageY - 28) + "px");
|
||||
})
|
||||
.on("mousemove", function(event) {
|
||||
tooltip.style("left", (event.pageX + 5) + "px")
|
||||
.style("top", (event.pageY - 28) + "px");
|
||||
})
|
||||
.on("mouseout", function() {
|
||||
tooltip.transition()
|
||||
.duration(500)
|
||||
.style("opacity", 0);
|
||||
});
|
||||
|
||||
svg.selectAll("text")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", d => xScale(d.start) + 5)
|
||||
.attr("y", 35)
|
||||
.text(d => d.obj)
|
||||
.attr("fill", "white")
|
||||
.style("pointer-events", "none");
|
||||
|
||||
// Date format
|
||||
const dateFormat = d3.timeFormat("%Y-%m-%d");
|
||||
|
||||
// x-axis
|
||||
const xAxis = d3.axisBottom(xScale)
|
||||
.ticks(d3.timeMonth.every(1))
|
||||
.tickFormat(dateFormat);
|
||||
|
||||
svg.append("g")
|
||||
.attr("transform", `translate(0,${height - margin.bottom})`)
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.attr("transform", "rotate(-45)")
|
||||
.style("text-anchor", "end");
|
||||
|
||||
const startDate = new Date(d3.min(data, d => d.start));
|
||||
const endDate = new Date(d3.max(data, d => d.end));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", margin.left)
|
||||
.attr("y", height - margin.bottom + 14)
|
||||
.style("text-anchor", "end")
|
||||
.style("font-size", "10px")
|
||||
.attr("transform", `rotate(-90, ${margin.left}, ${height - margin.bottom + 10})`)
|
||||
.text(dateFormat(startDate));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", width - margin.right)
|
||||
.attr("y", height - margin.bottom + 14)
|
||||
.style("text-anchor", "end")
|
||||
.style("font-size", "10px")
|
||||
.attr("transform", `rotate(-90, ${width - margin.right}, ${height - margin.bottom + 10})`)
|
||||
.text(dateFormat(endDate));
|
||||
|
||||
//return svg.node();
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
<table class="table">
|
||||
<thead class="">
|
||||
<tr>
|
||||
<th>username</th>
|
||||
<th>usernames</th>
|
||||
<th>ID</th>
|
||||
<th>First Seen</th>
|
||||
<th>Last Seen</th>
|
||||
|
@ -28,7 +28,17 @@
|
|||
</thead>
|
||||
<tbody style="font-size: 15px;">
|
||||
<tr>
|
||||
<td>{{ meta['username']['id'] }}</td>
|
||||
<td>
|
||||
{% if 'usernames' in meta %}
|
||||
<ul>
|
||||
{% for username in meta['usernames'] %}
|
||||
<li>{{ username['id'] }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ meta['username']['id'] }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ meta['id'] }}</td>
|
||||
<td>
|
||||
{% if meta['first_seen'] %}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<script src="{{ url_for('static', filename='js/d3.v7.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/d3/heatmap_week_hour.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/d3/chord_directed_diagram.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/d3/timeline_basic.js')}}"></script>
|
||||
|
||||
|
||||
</head>
|
||||
|
@ -47,6 +48,9 @@
|
|||
<h4 class="mx-5 mt-2 text-secondary">User All Messages:</h4>
|
||||
<div id="heatmapweekhourall"></div>
|
||||
|
||||
<h4>Usernames:</h4>
|
||||
<div id="timeline_user_usernames" style="max-width: 900px"></div>
|
||||
|
||||
<h4>Numbers of Messages Posted by Chat:</h4>
|
||||
<div id="chord_user_chats" style="max-width: 900px"></div>
|
||||
|
||||
|
@ -85,6 +89,13 @@
|
|||
{# {% endif %}#}
|
||||
});
|
||||
|
||||
|
||||
let url_t = "{{ url_for('chats_explorer.objects_user_account_usernames_timeline_json') }}?subtype={{ meta["subtype"] }}&id={{ meta["id"] }}"
|
||||
d3.json(url_t)
|
||||
.then(function(data) {
|
||||
create_timeline_basic('#timeline_user_usernames', data);
|
||||
});
|
||||
|
||||
d3.json("{{ url_for('chats_explorer.user_account_messages_stats_week_all') }}?subtype={{ meta['subtype'] }}&id={{ meta['id'] }}")
|
||||
.then(function(data) {
|
||||
create_heatmap_week_hour('#heatmapweekhourall', data);
|
||||
|
|
Loading…
Reference in New Issue