mirror of https://github.com/MISP/misp-dashboard
Improve trophy ranking algorithm, improved ui, added trophy leaderboard per category
parent
f22a9db067
commit
dc8a944b23
|
@ -80,7 +80,7 @@ regularlyDays=7
|
||||||
|
|
||||||
[TrophyDifficulty]
|
[TrophyDifficulty]
|
||||||
#represent the % of org that can have this rank. Rank 1 is ignored as only 1 org can have it.
|
#represent the % of org that can have this rank. Rank 1 is ignored as only 1 org can have it.
|
||||||
trophyMapping=[2, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10]
|
trophyMapping=[2, 9, 9, 10, 10, 16, 16, 10, 10, 4, 4]
|
||||||
|
|
||||||
[HonorTrophy]
|
[HonorTrophy]
|
||||||
0=No trophy
|
0=No trophy
|
||||||
|
|
|
@ -48,6 +48,7 @@ class Contributor_helper:
|
||||||
self.org_honor_badge_title[badgeNum] = self.cfg_org_rank.get('HonorBadge', str(badgeNum))
|
self.org_honor_badge_title[badgeNum] = self.cfg_org_rank.get('HonorBadge', str(badgeNum))
|
||||||
|
|
||||||
self.trophyMapping = json.loads(self.cfg_org_rank.get('TrophyDifficulty', 'trophyMapping'))
|
self.trophyMapping = json.loads(self.cfg_org_rank.get('TrophyDifficulty', 'trophyMapping'))
|
||||||
|
self.trophyMappingIncremental = [sum(self.trophyMapping[:i]) for i in range(len(self.trophyMapping)+1)]
|
||||||
self.trophyNum = len(self.cfg_org_rank.options('HonorTrophy'))-1 #0 is not a trophy
|
self.trophyNum = len(self.cfg_org_rank.options('HonorTrophy'))-1 #0 is not a trophy
|
||||||
self.categories_in_trophy = json.loads(self.cfg_org_rank.get('HonorTrophyCateg', 'categ'))
|
self.categories_in_trophy = json.loads(self.cfg_org_rank.get('HonorTrophyCateg', 'categ'))
|
||||||
self.trophy_title = {}
|
self.trophy_title = {}
|
||||||
|
@ -352,7 +353,6 @@ class Contributor_helper:
|
||||||
|
|
||||||
''' TROPHIES '''
|
''' TROPHIES '''
|
||||||
def getOrgTrophies(self, org):
|
def getOrgTrophies(self, org):
|
||||||
self.getAllOrgsTrophyRanking()
|
|
||||||
keyname = '{mainKey}:{orgCateg}'
|
keyname = '{mainKey}:{orgCateg}'
|
||||||
trophy = []
|
trophy = []
|
||||||
for categ in self.categories_in_trophy:
|
for categ in self.categories_in_trophy:
|
||||||
|
@ -360,12 +360,13 @@ class Contributor_helper:
|
||||||
totNum = self.serv_redis_db.zcard(key)
|
totNum = self.serv_redis_db.zcard(key)
|
||||||
if totNum == 0:
|
if totNum == 0:
|
||||||
continue
|
continue
|
||||||
pos = self.serv_redis_db.zrank(key, org)
|
pos = self.serv_redis_db.zrevrank(key, org)
|
||||||
if pos is None:
|
if pos is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
trophy_rank = self.posToRankMapping(pos, totNum)
|
trophy_rank = self.posToRankMapping(pos, totNum)
|
||||||
trophy_Pnts = self.serv_redis_db.zscore(key, org)
|
trophy_Pnts = self.serv_redis_db.zscore(key, org)
|
||||||
trophy.append({ 'categ': categ, 'trophy_points': trophy_Pnts, 'trophy_rank': trophy_rank, 'trophy_true_rank': trophy_rank, 'trophy_title': self.trophy_title[trophy_rank]})
|
trophy.append({ 'categ': categ, 'trophy_points': trophy_Pnts, 'trophy_rank': trophy_rank, 'trophy_true_rank': self.trophyNum-trophy_rank, 'trophy_title': self.trophy_title[trophy_rank]})
|
||||||
return trophy
|
return trophy
|
||||||
|
|
||||||
def getOrgsTrophyRanking(self, categ):
|
def getOrgsTrophyRanking(self, categ):
|
||||||
|
@ -374,27 +375,29 @@ class Contributor_helper:
|
||||||
res = [[org.decode('utf8'), score] for org, score in res]
|
res = [[org.decode('utf8'), score] for org, score in res]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getAllOrgsTrophyRanking(self):
|
def getAllOrgsTrophyRanking(self, category=None):
|
||||||
|
concerned_categ = self.categories_in_trophy if category is None else category
|
||||||
dico_categ = {}
|
dico_categ = {}
|
||||||
for categ in self.categories_in_trophy:
|
for categ in [concerned_categ]:
|
||||||
res = self.getOrgsTrophyRanking(categ)
|
res = self.getOrgsTrophyRanking(categ)
|
||||||
|
# add ranking info
|
||||||
|
tot = len(res)
|
||||||
|
for pos in range(tot):
|
||||||
|
res[pos].append(self.trophyNum-self.posToRankMapping(pos, tot))
|
||||||
dico_categ[categ] = res
|
dico_categ[categ] = res
|
||||||
|
toret = dico_categ if category is None else dico_categ.get(category, [])
|
||||||
|
return toret
|
||||||
|
|
||||||
def posToRankMapping(self, pos, totNum):
|
def posToRankMapping(self, pos, totNum):
|
||||||
mapping = self.trophyMapping
|
ratio = pos/totNum*100
|
||||||
mapping_num = [math.ceil(float(float(totNum*i)/float(100))) for i in mapping]
|
rank = 0
|
||||||
if pos == 0: #first
|
if pos == totNum:
|
||||||
position = 1
|
return 0
|
||||||
else:
|
else:
|
||||||
temp_pos = pos
|
for i in range(len(self.trophyMappingIncremental)):
|
||||||
counter = 1
|
if self.trophyMappingIncremental[i] < ratio <= self.trophyMappingIncremental[i+1]:
|
||||||
for num in mapping_num:
|
rank = i+1
|
||||||
if temp_pos < num:
|
return rank
|
||||||
position = counter
|
|
||||||
else:
|
|
||||||
temp_pos -= num
|
|
||||||
counter += 1
|
|
||||||
return self.trophyNum+1 - position
|
|
||||||
|
|
||||||
def giveTrophyPointsToOrg(self, org, categ, points):
|
def giveTrophyPointsToOrg(self, org, categ, points):
|
||||||
keyname = '{mainKey}:{orgCateg}'
|
keyname = '{mainKey}:{orgCateg}'
|
||||||
|
|
|
@ -429,6 +429,11 @@ def getTrophies():
|
||||||
org = ''
|
org = ''
|
||||||
return jsonify(contributor_helper.getOrgTrophies(org))
|
return jsonify(contributor_helper.getOrgTrophies(org))
|
||||||
|
|
||||||
|
@app.route("/_getAllOrgsTrophyRanking")
|
||||||
|
@app.route("/_getAllOrgsTrophyRanking/<string:categ>")
|
||||||
|
def getAllOrgsTrophyRanking(categ=None):
|
||||||
|
return jsonify(contributor_helper.getAllOrgsTrophyRanking(categ))
|
||||||
|
|
||||||
|
|
||||||
''' USERS '''
|
''' USERS '''
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,13 @@
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.modal-xl {
|
||||||
|
width: 90%;
|
||||||
|
max-width:1500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.successCell {
|
.successCell {
|
||||||
background-color: #dff0d8 !important
|
background-color: #dff0d8 !important
|
||||||
}
|
}
|
||||||
|
@ -191,6 +198,19 @@
|
||||||
margin-right:auto;
|
margin-right:auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.allOrgRankingDiv {
|
||||||
|
overflow: scroll;
|
||||||
|
max-height: 600px;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-background-clip: padding-box;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border: 1px solid rgba(0,0,0,.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||||
|
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -551,6 +551,27 @@ function updateProgressHeader(org) {
|
||||||
updateOvertakePnts();
|
updateOvertakePnts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generate_table_ranking_on_category(categ) {
|
||||||
|
$.getJSON( url_getAllOrgsTrophyRanking+'/'+categ, function( data ) {
|
||||||
|
var body = $('#bodyTableThropyAllOrgRankingModal');
|
||||||
|
body.empty();
|
||||||
|
data.forEach(function(arr, i) {
|
||||||
|
var org = arr[0];
|
||||||
|
var points = arr[1];
|
||||||
|
var rank = arr[2];
|
||||||
|
var tr = $('<tr></tr>');
|
||||||
|
tr.append($('<td style="width: 100px;">'+i+'</td>'));
|
||||||
|
tr.append($('<td style="width: 100px;"><img src="'+url_baseTrophyLogo+rank+'.png" width="30" height="30"></td>'));
|
||||||
|
tr.append($('<td style="width: 200px;">'+points+'</td>'));
|
||||||
|
tr.append($('<td><a href="?org='+org+'">'+org+'</a></td>'));
|
||||||
|
if (currOrg == org) {
|
||||||
|
tr.addClass('selectedOrgInTable');
|
||||||
|
}
|
||||||
|
body.append(tr);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function update_timeout_last_added_contrib() {
|
function update_timeout_last_added_contrib() {
|
||||||
clearTimeout(timeout_last_added_contrib);
|
clearTimeout(timeout_last_added_contrib);
|
||||||
timeout_last_added_contrib = setTimeout(function() {
|
timeout_last_added_contrib = setTimeout(function() {
|
||||||
|
@ -661,4 +682,13 @@ $(document).ready(function() {
|
||||||
addAwards(datatableAwards, json, true);
|
addAwards(datatableAwards, json, true);
|
||||||
updateProgressHeader(currOrg);
|
updateProgressHeader(currOrg);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$('#bodyTableTrophyModalOrg input').off('click').on('click', function(e) {
|
||||||
|
var categ = $(this).data('category');
|
||||||
|
var tds = $('#bodyTableTrophyModalOrg td');
|
||||||
|
tds.removeClass('success');
|
||||||
|
$(this).parent().addClass('success');
|
||||||
|
generate_table_ranking_on_category(categ);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -148,7 +148,7 @@
|
||||||
|
|
||||||
<!-- Modal trophy -->
|
<!-- Modal trophy -->
|
||||||
<div id="myModalTrophy" class="modal fade" role="dialog">
|
<div id="myModalTrophy" class="modal fade" role="dialog">
|
||||||
<div class="modal-dialog modal-lg" style="width: 1500px;">
|
<div class="modal-dialog modal-xl">
|
||||||
<!-- Modal content-->
|
<!-- Modal content-->
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
@ -181,32 +181,35 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<p style="font-size: 18px; display: inline;">Trophies: </p><p style="display: inline;">Shows your skills in information sharing </p><i> (earned via upvotes or sightings from other organisation)</i>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div>
|
||||||
<table class="table table-striped table-bordered">
|
<p style="font-size: 18px; display: inline;">Trophies: </p><a style="display: inline;" class="collapsed" data-toggle="collapse" href="#collapsibleTrophyInfo" aria-expanded="false">Shows your skills in information sharing <span class="fa fa-caret-down"></span></a><i> (earned via upvotes or sightings from other organisation)</i>
|
||||||
<thead>
|
|
||||||
<tr>
|
<div id="collapsibleTrophyInfo" class="table-responsive collapse">
|
||||||
{% for title in trophy_title_str %}
|
<table class="table table-striped table-bordered">
|
||||||
<th class="centerCell">{{ title }}</th>
|
<thead>
|
||||||
{% endfor %}
|
<tr>
|
||||||
</tr>
|
{% for title in trophy_title_str %}
|
||||||
</thead>
|
<th class="centerCell">{{ title }}</th>
|
||||||
<tbody id='bodyTableTrophyModal'>
|
{% endfor %}
|
||||||
<tr>
|
</tr>
|
||||||
{% for perc in trophy_mapping %}
|
</thead>
|
||||||
<td class="centerCell">{{ perc }}</td>
|
<tbody id='bodyTableTrophyModal'>
|
||||||
{% endfor %}
|
<tr>
|
||||||
</tr>
|
{% for perc in trophy_mapping %}
|
||||||
<tr>
|
<td class="centerCell">{{ perc }}</td>
|
||||||
{% for title in trophy_title_str %}
|
{% endfor %}
|
||||||
<td>
|
</tr>
|
||||||
<input type='image' style="display: block; margin-left: auto; margin-right: auto;" height="64" width="64" src="{{ url_for('static', filename='pics/MISPTrophy/'+loop.index0|string+'.png') }}">
|
<tr>
|
||||||
</td>
|
{% for title in trophy_title_str %}
|
||||||
{% endfor %}
|
<td>
|
||||||
</tr>
|
<input type='image' style="display: block; margin-left: auto; margin-right: auto;" height="64" width="64" src="{{ url_for('static', filename='pics/MISPTrophy/'+loop.index0|string+'.png') }}">
|
||||||
</tbody>
|
</td>
|
||||||
</table>
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="font-size: 18px; display: inline;">Acquired trophies: </p>
|
<p style="font-size: 18px; display: inline;">Acquired trophies: </p>
|
||||||
|
@ -219,11 +222,11 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id='bodyTableTrophyModal'>
|
<tbody id='bodyTableTrophyModalOrg'>
|
||||||
<tr>
|
<tr>
|
||||||
{% for categ in trophy_categ_list_id %}
|
{% for categ in trophy_categ_list_id %}
|
||||||
<td>
|
<td>
|
||||||
<input type='image' id='trophy_{{categ}}' style="display: block; margin-left: auto; margin-right: auto;" height="64" width="64" src="{{ url_for('static', filename='pics/MISPTrophy/0.png') }}">
|
<input type='image' id='trophy_{{categ}}' data-category='{{categ}}' style="display: block; margin-left: auto; margin-right: auto;" height="64" width="64" src="{{ url_for('static', filename='pics/MISPTrophy/0.png') }}">
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -231,6 +234,22 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class='allOrgRankingDiv'>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Rank</th>
|
||||||
|
<th>Trophy</th>
|
||||||
|
<th>Points</th>
|
||||||
|
<th>Org</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id='bodyTableThropyAllOrgRankingModal'>
|
||||||
|
<tr><td>Click on a category to view the global ranking</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -510,6 +529,7 @@
|
||||||
var url_getContributionOrgStatus = "{{ url_for('getContributionOrgStatus') }}";
|
var url_getContributionOrgStatus = "{{ url_for('getContributionOrgStatus') }}";
|
||||||
var url_getHonorBadges = "{{ url_for('getHonorBadges') }}";
|
var url_getHonorBadges = "{{ url_for('getHonorBadges') }}";
|
||||||
var url_getTrophies = "{{ url_for('getTrophies')}}"
|
var url_getTrophies = "{{ url_for('getTrophies')}}"
|
||||||
|
var url_getAllOrgsTrophyRanking = "{{ url_for('getAllOrgsTrophyRanking')}}"
|
||||||
|
|
||||||
var url_baseRankMonthlyLogo = "{{ url_for('static', filename='pics/rankingMISPMonthly/1.svg') }}";
|
var url_baseRankMonthlyLogo = "{{ url_for('static', filename='pics/rankingMISPMonthly/1.svg') }}";
|
||||||
url_baseRankMonthlyLogo = url_baseRankMonthlyLogo.substring(0, url_baseRankMonthlyLogo.length-5);
|
url_baseRankMonthlyLogo = url_baseRankMonthlyLogo.substring(0, url_baseRankMonthlyLogo.length-5);
|
||||||
|
|
Loading…
Reference in New Issue