parent
bc58b8c9c4
commit
0a91f820f3
@ -0,0 +1,130 @@ |
||||
"""Provide integration with ROAR.""" |
||||
# Bodhgaya, India (APR/19) |
||||
|
||||
import os.path |
||||
import threading |
||||
import json |
||||
import time |
||||
import datetime |
||||
import tempfile |
||||
import logging |
||||
import urllib.request |
||||
|
||||
from flask import render_template, jsonify |
||||
|
||||
from vasl_templates.webapp import app |
||||
|
||||
_roar_scenario_index = {} |
||||
_roar_scenario_index_lock = threading.Lock() |
||||
|
||||
_logger = logging.getLogger( "roar" ) |
||||
|
||||
ROAR_SCENARIO_INDEX_URL = "http://vasl-templates.org/services/roar/scenario-index.json" |
||||
CACHE_TTL = 6 * 60*60 |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def init_roar( msg_store ): |
||||
"""Initialize ROAR integration.""" |
||||
|
||||
# initialize |
||||
download = True |
||||
cache_fname = os.path.join( tempfile.gettempdir(), "vasl-templates.roar-scenario-index.json" ) |
||||
enable_cache = not app.config.get( "DISABLE_ROAR_SCENARIO_INDEX_CACHE" ) |
||||
if not enable_cache: |
||||
cache_fname = None |
||||
|
||||
# check if we have a cached copy of the scenario index |
||||
if enable_cache and os.path.isfile( cache_fname ): |
||||
# yup - load it, so that we have something until we finish downloading a fresh copy |
||||
_logger.info( "Loading cached ROAR scenario index: %s", cache_fname ) |
||||
with open( cache_fname, "r" ) as fp: |
||||
_load_roar_scenario_index( fp.read(), "cached", msg_store ) |
||||
# check if we should download a fresh copy |
||||
mtime = os.path.getmtime( cache_fname ) |
||||
age = int( time.time() - mtime ) |
||||
_logger.debug( "Cached scenario index age: %s (ttl=%s) (mtime=%s)", |
||||
datetime.timedelta(seconds=age), datetime.timedelta(seconds=CACHE_TTL), |
||||
time.strftime( "%Y-%m-%d %H:%M:%S", time.gmtime(mtime) ) |
||||
) |
||||
if age < CACHE_TTL: |
||||
download = False |
||||
|
||||
# check if we should download the ROAR scenario index |
||||
if download: |
||||
if app.config.get("DISABLE_ROAR_SCENARIO_INDEX_DOWNLOAD"): |
||||
_logger.warning( "Downloading the ROAR scenario index has been disabled." ) |
||||
else: |
||||
# yup - make it so (nb: we do it in a background thread to avoid blocking the startup process) |
||||
# NOTE: This is the only place we do this, so if it fails, the program needs to be restarted to try again. |
||||
# This is not great, but we can live it (e.g. we will generally be using the cached copy). |
||||
threading.Thread( target = _download_roar_scenario_index, |
||||
args = ( cache_fname, msg_store ) |
||||
).start() |
||||
|
||||
def _download_roar_scenario_index( save_fname, msg_store ): |
||||
"""Download the ROAR scenario index.""" |
||||
|
||||
# download the ROAR scenario index |
||||
url = app.config.get( "ROAR_SCENARIO_INDEX_URL", "https://vasl-templates.org/services/roar/scenario-index.json" ) |
||||
_logger.info( "Downloading ROAR scenario index: %s", url ) |
||||
try: |
||||
fp = urllib.request.urlopen( url ) |
||||
data = fp.read().decode( "utf-8" ) |
||||
except Exception as ex: #pylint: disable=broad-except |
||||
# NOTE: We catch all exceptions, since we don't want an error here to stop us from running :-/ |
||||
error_msg = "Can't download ROAR scenario index: {}".format( getattr(ex,"reason",str(ex)) ) |
||||
_logger.warning( error_msg ) |
||||
if msg_store: |
||||
msg_store.warning( error_msg ) |
||||
return |
||||
if not _load_roar_scenario_index( data, "downloaded", msg_store ): |
||||
# NOTE: If we fail to load the scenario index (e.g. because of invalid JSON), we exit here |
||||
# and won't overwrite the cached copy of the file with the bad data. |
||||
return |
||||
|
||||
# save a copy of the data |
||||
if save_fname: |
||||
_logger.debug( "Saving a copy of the ROAR scenario index: %s", save_fname ) |
||||
with open( save_fname, "w" ) as fp: |
||||
fp.write( data ) |
||||
|
||||
def _load_roar_scenario_index( data, data_type, msg_store ): |
||||
"""Load the ROAR scenario index.""" |
||||
|
||||
# load the ROAR scenario index |
||||
try: |
||||
scenario_index = json.loads( data ) |
||||
except Exception as ex: #pylint: disable=broad-except |
||||
# NOTE: We catch all exceptions, since we don't want an error here to stop us from running :-/ |
||||
error_msg = "Can't load {} ROAR scenario index: {}".format( data_type, ex ) |
||||
_logger.warning( error_msg ) |
||||
if msg_store: |
||||
msg_store.warning( error_msg ) |
||||
return False |
||||
_logger.debug( "Loaded %s ROAR scenario index OK: #scenarios=%d", data_type, len(scenario_index) ) |
||||
_logger.debug( "- Last updated: %s", scenario_index.get( "_lastUpdated_", "n/a" ) ) |
||||
_logger.debug( "- # playings: %s", str( scenario_index.get( "_nPlayings_", "n/a" ) ) ) |
||||
_logger.debug( "- Generated at: %s", scenario_index.get( "_generatedAt_", "n/a" ) ) |
||||
|
||||
# install the new ROAR scenario index |
||||
with _roar_scenario_index_lock: |
||||
global _roar_scenario_index |
||||
_roar_scenario_index = scenario_index |
||||
|
||||
return True |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
@app.route( "/roar/scenario-index" ) |
||||
def get_roar_scenario_index(): |
||||
"""Return the ROAR scenario index.""" |
||||
with _roar_scenario_index_lock: |
||||
return jsonify( _roar_scenario_index ) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
@app.route( "/roar/check" ) |
||||
def check_roar(): |
||||
"""Check the ROAR data (for testing porpoises only).""" |
||||
return render_template( "check-roar.html" ) |
@ -0,0 +1,19 @@ |
||||
#select-roar-scenario { overflow: hidden ; } |
||||
|
||||
.ui-dialog.select-roar-scenario .ui-dialog-titlebar { background: #ffffcc ; border: 1px solid #e0e0cc ; } |
||||
.ui-dialog.select-roar-scenario .ui-dialog-content { padding-top: 0 !important ; } |
||||
.ui-dialog.select-roar-scenario .ui-dialog-buttonpane { border: none ; margin-top: 0 !important ; padding-top: 0 !important ; } |
||||
|
||||
#select-roar-scenario .header { height: 1.75em ; margin-top: 0.25em ; font-size: 80% ; } |
||||
|
||||
#select-roar-scenario .select2-selection { display: none ; } |
||||
#select-roar-scenario .select2-search { padding: 0 0 5px 0 ; } |
||||
#select-roar-scenario .select2-results { border: 1px solid #ccc ; } |
||||
#select-roar-scenario .select2-results__options { max-height: none ; } |
||||
#select-roar-scenario .select2-dropdown { border: none ; } |
||||
|
||||
#select-roar-scenario .select2-dropdown .scenario .scenario-id { font-size: 90% ; color: #666 ; } |
||||
#select-roar-scenario .select2-dropdown .scenario .publication { display: block ; font-size: 80% ; font-style: italic ; color: #888 ; } |
||||
|
||||
#select-roar-scenario .select2-results__option--highlighted[aria-selected] .scenario-id { color: #eee ; } |
||||
#select-roar-scenario .select2-results__option--highlighted[aria-selected] .publication { color: #eee ; } |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
@ -0,0 +1,310 @@ |
||||
gRoarScenarioIndex = null ; |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function _get_roar_scenario_index( on_ready ) |
||||
{ |
||||
// check if we already have the ROAR scenario index
|
||||
if ( gRoarScenarioIndex && Object.keys(gRoarScenarioIndex).length > 0 ) { |
||||
// yup - just do it
|
||||
on_ready() ; |
||||
} else { |
||||
// nope - download it (nb: we do this on-demand, instead of during startup,
|
||||
// to give the backend time if it wants to download a fresh copy).
|
||||
// NOTE: We will also get here if we downloaded the scenario index, but it's empty.
|
||||
// This can happen if the cached file is not there, and the server is still downloading
|
||||
// a fresh copy, in which case, we will keep retrying until we get something.
|
||||
$.getJSON( gGetRoarScenarioIndexUrl, function(data) { |
||||
gRoarScenarioIndex = data ; |
||||
on_ready() ; |
||||
} ).fail( function( xhr, status, errorMsg ) { |
||||
showErrorMsg( "Can't get the ROAR scenario index:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ; |
||||
} ) ; |
||||
} |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function search_roar() |
||||
{ |
||||
var unknown_nats = [] ; |
||||
function on_scenario_selected( roar_id ) { |
||||
// update the UI for the selected ROAR scenario
|
||||
set_roar_scenario( roar_id ) ; |
||||
// populate the scenario name/ID
|
||||
var scenario = gRoarScenarioIndex[ roar_id ] ; |
||||
if ( $("input[name='SCENARIO_NAME']").val() === "" && $("input[name='SCENARIO_ID']").val() === "" ) { |
||||
$("input[name='SCENARIO_NAME']").val( scenario.name ) ; |
||||
$("input[name='SCENARIO_ID']").val( scenario.scenario_id ) ; |
||||
} |
||||
// update the player nationalities
|
||||
// NOTE: The player order as returned by ROAR is undetermined (and could change from call to call),
|
||||
// so what we set here might not match what's in the scenario card, but we've got a 50-50 chance of being right... :-/
|
||||
update_player( scenario, 1 ) ; |
||||
update_player( scenario, 2 ) ; |
||||
if ( unknown_nats.length > 0 ) { |
||||
var buf = [ "Unrecognized nationality in ROAR:", "<ul>" ] ; |
||||
for ( var i=0 ; i < unknown_nats.length ; ++i ) |
||||
buf.push( "<li>" + unknown_nats[i] ) ; |
||||
buf.push( "</ul>" ) ; |
||||
showWarningMsg( buf.join("") ) ; |
||||
} |
||||
} |
||||
|
||||
function update_player( scenario, player_no ) { |
||||
var roar_nat = scenario.results[ player_no-1 ][0] ; |
||||
var nat = convert_roar_nat( roar_nat ) ; |
||||
if ( ! nat ) { |
||||
unknown_nats.push( roar_nat ) ; |
||||
return ; |
||||
} |
||||
if ( nat === get_player_nat( player_no ) ) |
||||
return ; |
||||
if ( ! is_player_ob_empty( player_no ) ) |
||||
return ; |
||||
$( "select[name='PLAYER_" + player_no + "']" ).val( nat ).trigger( "change" ) ; |
||||
on_player_change( player_no ) ; |
||||
} |
||||
|
||||
// ask the user to select a ROAR scenario
|
||||
_get_roar_scenario_index( function() { |
||||
do_search_roar( on_scenario_selected ) ; |
||||
} ) ; |
||||
} |
||||
|
||||
function do_search_roar( on_ok ) |
||||
{ |
||||
// initialize the select2
|
||||
var $sel = $( "#select-roar-scenario select" ) ; |
||||
$sel.select2( { |
||||
width: "100%", |
||||
templateResult: function( opt ) { return opt.id ? _format_entry(opt.id) : opt.text ; }, |
||||
dropdownParent: $("#select-roar-scenario"), // FUDGE! need this for the searchbox to work :-/
|
||||
closeOnSelect: false, |
||||
} ) ; |
||||
|
||||
// stop the select2 droplist from closing up
|
||||
$sel.on( "select2:closing", function(evt) { |
||||
evt.preventDefault() ; |
||||
} ) ; |
||||
|
||||
// let the user select a scenario
|
||||
function on_resize( $dlg ) { |
||||
$( ".select2-results ul" ).height( $dlg.height() - 50 ) ; |
||||
} |
||||
var $dlg = $("#select-roar-scenario").dialog( { |
||||
title: "Search ROAR", |
||||
dialogClass: "select-roar-scenario", |
||||
modal: true, |
||||
minWidth: 400, |
||||
minHeight: 350, |
||||
create: function() { |
||||
// initialize the dialog
|
||||
init_dialog( $(this), "OK", false ) ; |
||||
// handle ENTER and double-click
|
||||
function auto_select_scenario( evt ) { |
||||
if ( $sel.val() ) { |
||||
$( ".ui-dialog.select-roar-scenario button:contains('OK')" ).click() ; |
||||
evt.preventDefault() ; |
||||
} |
||||
} |
||||
$("#select-roar-scenario").keydown( function(evt) { |
||||
if ( evt.keyCode == $.ui.keyCode.ENTER ) |
||||
auto_select_scenario( evt ) ; |
||||
else if ( evt.keyCode == $.ui.keyCode.ESCAPE ) |
||||
$(this).dialog( "close" ) ; |
||||
} ).dblclick( function(evt) { |
||||
auto_select_scenario( evt ) ; |
||||
} ) ; |
||||
}, |
||||
open: function() { |
||||
// initialize
|
||||
// NOTE: We do this herem instead of in the "create" handler, to handle the case
|
||||
// where the scenario index was initially unavailable but the download has since completed.
|
||||
_load_select2( $sel ) ; |
||||
on_dialog_open( $(this) ) ; |
||||
$sel.select2( "open" ) ; |
||||
// update the UI
|
||||
on_resize( $(this) ) ; |
||||
}, |
||||
resize: function() { on_resize( $(this) ) ; }, |
||||
buttons: { |
||||
OK: function() { |
||||
// notify the caller about the selected scenario
|
||||
var roar_id = $sel.select2("data")[0].id ; |
||||
on_ok( roar_id ) ; |
||||
$dlg.dialog( "close" ) ; |
||||
}, |
||||
Cancel: function() { $(this).dialog( "close" ) ; }, |
||||
}, |
||||
} ) ; |
||||
} |
||||
|
||||
function _load_select2( $sel ) |
||||
{ |
||||
function remove_quotes( lquote, rquote ) { |
||||
var len = name.length ; |
||||
if ( name.substr( 0, lquote.length ) === lquote && name.substr( len-rquote.length ) === rquote ) |
||||
name = name.substr( lquote.length, len-lquote.length-rquote.length ) ; |
||||
if ( name.substr( 0, lquote.length ) == lquote ) |
||||
name = name.substr( lquote.length ) ; |
||||
return name ; |
||||
} |
||||
|
||||
// sort the scenarios
|
||||
var roar_ids=[], roar_id, scenario ; |
||||
for ( roar_id in gRoarScenarioIndex ) { |
||||
if ( roar_id[0] === "_" ) |
||||
continue ; |
||||
roar_ids.push( roar_id ) ; |
||||
scenario = gRoarScenarioIndex[ roar_id ] ; |
||||
var name = scenario.name ; |
||||
name = remove_quotes( '"', '"' ) ; |
||||
name = remove_quotes( "'", "'" ) ; |
||||
name = remove_quotes( """, """ ) ; |
||||
name = remove_quotes( "\u2018", "\u2019" ) ; |
||||
name = remove_quotes( "\u201c", "\u201d" ) ; |
||||
if ( name.substring(0,3) === "..." ) |
||||
name = name.substr( 3 ) ; |
||||
scenario._sort_name = name.trim().toUpperCase() ; |
||||
} |
||||
roar_ids.sort( function( lhs, rhs ) { |
||||
lhs = gRoarScenarioIndex[ lhs ]._sort_name ; |
||||
rhs = gRoarScenarioIndex[ rhs ]._sort_name ; |
||||
if ( lhs < rhs ) |
||||
return -1 ; |
||||
else if ( lhs > rhs ) |
||||
return +1 ; |
||||
return 0 ; |
||||
} ) ; |
||||
|
||||
// get the currently-active ROAR scenario
|
||||
var curr_roar_id = $("input[name='ROAR_ID']").val() ; |
||||
|
||||
// load the select2
|
||||
var buf = [] ; |
||||
for ( var i=0 ; i < roar_ids.length ; ++i ) { |
||||
roar_id = roar_ids[ i ] ; |
||||
scenario = gRoarScenarioIndex[ roar_id ] ; |
||||
// NOTE: The <option> text is what gets searched (_format_entry() generates what gets shown),
|
||||
// so we include the scenario ID here, so that it also becomes searchable.
|
||||
buf.push( "<option value='" + roar_id + "'" ) ; |
||||
if ( roar_id === curr_roar_id ) { |
||||
// FIXME! How can we scroll this into view? Calling scrollIntoView(),
|
||||
// even in the "open" handler, causes weird problems.
|
||||
buf.push( " selected" ) ; |
||||
} |
||||
buf.push( ">" ) ; |
||||
buf.push( scenario.name + " " + scenario.scenario_id, |
||||
"</option>" |
||||
) ; |
||||
} |
||||
$sel.html( buf.join("") ) ; |
||||
} |
||||
|
||||
function _format_entry( roar_id ) { |
||||
// generate the HTML for a scenario
|
||||
var scenario = gRoarScenarioIndex[ roar_id ] ; |
||||
var buf = [ "<div class='scenario' data-roarid='", roar_id , "'>", |
||||
scenario.name, |
||||
" <span class='scenario-id'>[", strReplaceAll(scenario.scenario_id," "," "), "]</span>", |
||||
" <span class='publication'>", scenario.publication, "</span>", |
||||
"</div>" |
||||
] ; |
||||
return $( buf.join("") ) ; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function disconnect_roar() |
||||
{ |
||||
// disconnect from the ROAR scenario
|
||||
set_roar_scenario( null ) ; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function go_to_roar_scenario() |
||||
{ |
||||
// go the currently-active ROAR scenario
|
||||
var roar_id = $( "input[name='ROAR_ID']" ).val() ; |
||||
var url = gRoarScenarioIndex[ roar_id ].url ; |
||||
if ( gWebChannelHandler ) |
||||
window.location = url ; // nb: AppWebPage will intercept this and launch a new browser window
|
||||
else |
||||
window.open( url ) ; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function set_roar_scenario( roar_id ) |
||||
{ |
||||
var total_playings ; |
||||
function safe_score( nplayings ) { return total_playings === 0 ? 0 : nplayings / total_playings ; } |
||||
function get_label( score ) { return total_playings === 0 ? "" : percentString( score ) ; } |
||||
|
||||
function do_set_roar_scenaro() { |
||||
if ( roar_id ) { |
||||
// save the ROAR ID
|
||||
$( "input[name='ROAR_ID']" ).val( roar_id ) ; |
||||
// update the progress bars
|
||||
var scenario = gRoarScenarioIndex[ roar_id ] ; |
||||
if ( ! scenario ) |
||||
return ; |
||||
var results = scenario.results ; |
||||
if ( convert_roar_nat(results[0][0]) === get_player_nat(2) || convert_roar_nat(results[1][0]) === get_player_nat(1) ) { |
||||
// FUDGE! The order of players returned by ROAR is indeterminate (and could change from call to call),
|
||||
// so we try to show the results in the way that best matches what's on-screen.
|
||||
results = [ results[1], results[0] ] ; |
||||
} |
||||
total_playings = results[0][1] + results[1][1] ; |
||||
$( "#roar-info .name.player1" ).html( results[0][0] ) ; |
||||
$( "#roar-info .count.player1" ).html( "(" + results[0][1] + ")" ) ; |
||||
var score = 100 * safe_score( results[0][1] ) ; |
||||
$( "#roar-info .progressbar.player1" ).progressbar( { value: 100-score } ) |
||||
.find( ".label" ).text( get_label( score ) ) ; |
||||
$( "#roar-info .name.player2" ).html( results[1][0] ) ; |
||||
$( "#roar-info .count.player2" ).html( "(" + results[1][1] + ")" ) ; |
||||
score = 100 * safe_score( results[1][1] ) ; |
||||
$( "#roar-info .progressbar.player2" ).progressbar( { value: score } ) |
||||
.find( ".label" ).text( get_label( score ) ) ; |
||||
// show the ROAR scenario details
|
||||
$( "#go-to-roar" ).attr( "title", scenario.name+" ["+scenario.scenario_id+"]\n" + scenario.publication ) ; |
||||
// NOTE: We see the fade in if the panel is already visible and we load a scenario that has a ROAR ID,
|
||||
// because we reset the scenario the scenario before loading another one, which causes the panel
|
||||
// to be hidden. Fixing this is more trouble than it's worth... :-/
|
||||
$( "#roar-info" ).fadeIn( 1*1000 ) ; |
||||
} else { |
||||
// there is no associated ROAR scenario - hide the info panel
|
||||
$( "input[name='ROAR_ID']" ).val( "" ) ; |
||||
$( "#roar-info" ).hide() ; |
||||
} |
||||
} |
||||
|
||||
// set the ROAR scenario
|
||||
_get_roar_scenario_index( do_set_roar_scenaro ) ; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function convert_roar_nat( roar_nat ) |
||||
{ |
||||
// clean up the ROAR nationality
|
||||
roar_nat = roar_nat.toUpperCase() ; |
||||
var pos = roar_nat.indexOf( "/" ) ; |
||||
if ( pos > 0 ) |
||||
roar_nat = roar_nat.substr( 0, pos ) ; // e.g. "British/Partisan" -> "British"
|
||||
else { |
||||
var match = roar_nat.match( /\(.*\)$/ ) ; |
||||
if ( match ) |
||||
roar_nat = roar_nat.substr( 0, roar_nat.length-match[0].length ).trim() ; // e.g. "Thai (Chinese)" -> "Thai"
|
||||
} |
||||
|
||||
// try to match the ROAR nationality with one of ours
|
||||
for ( var nat in gTemplatePack.nationalities ) { |
||||
if ( roar_nat === gTemplatePack.nationalities[nat].display_name.toUpperCase() ) |
||||
return nat ; |
||||
} |
||||
|
||||
return null ; |
||||
} |
@ -0,0 +1,120 @@ |
||||
<!doctype html> <!-- NOTE: For testing porpoises only! --> |
||||
<html lang="en"> |
||||
|
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<style> |
||||
th, td { padding: 2px 5px ; text-align: left ; } |
||||
th { background: #eee ; } |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
<div id="results" style="display:none;"></div> |
||||
</body> |
||||
|
||||
<script src="{{url_for('static',filename='jquery/jquery-3.3.1.min.js')}}"></script> |
||||
<script src="{{url_for('static',filename='roar.js')}}"></script> |
||||
|
||||
<script> |
||||
gRoarScenarioIndex = null ; |
||||
gTemplatePack = null ; |
||||
</script> |
||||
|
||||
<script> |
||||
$(document).ready( function () { |
||||
|
||||
// initialize |
||||
var on_load_counter = 2 ; |
||||
function on_data_loaded() { |
||||
if ( --on_load_counter == 0 ) { |
||||
// everything's loaded - generate the report |
||||
check_roar() ; |
||||
} |
||||
} |
||||
|
||||
// get the ROAR scenario index |
||||
$.getJSON( "{{url_for('get_roar_scenario_index')}}", function(data) { |
||||
gRoarScenarioIndex = data ; |
||||
on_data_loaded() ; |
||||
} ).fail( function( xhr, status, errorMsg ) { |
||||
alert( "Can't get the ROAR scenario index:\n\n" + errorMsg ) ; |
||||
} ) ; |
||||
|
||||
// get the template pack |
||||
$.getJSON( "{{url_for('get_template_pack')}}", function(data) { |
||||
gTemplatePack = data ; |
||||
on_data_loaded() ; |
||||
} ).fail( function( xhr, status, errorMsg ) { |
||||
alert( "Can't get the template pack:\n\n" + errorMsg ) ; |
||||
} ) ; |
||||
} ) ; |
||||
|
||||
function check_roar() |
||||
{ |
||||
// initialize |
||||
var buf = [] ; |
||||
|
||||
// generate the list of nationalities in ROAR |
||||
var roar_nats = {} ; |
||||
function on_nat( nat ) { |
||||
if ( nat in roar_nats ) |
||||
++ roar_nats[nat] ; |
||||
else |
||||
roar_nats[nat] = 1 ; |
||||
} |
||||
for ( var roar_id in gRoarScenarioIndex ) { |
||||
if ( roar_id[0] == "_" ) |
||||
continue ; |
||||
var scenario = gRoarScenarioIndex[ roar_id ] ; |
||||
on_nat( scenario.results[0][0] ) ; |
||||
on_nat( scenario.results[1][0] ) ; |
||||
} |
||||
|
||||
// sort the results |
||||
var roar_nats_sorted = Object.keys( roar_nats ) ; |
||||
roar_nats_sorted.sort( function( lhs, rhs ) { |
||||
if ( roar_nats[lhs] < roar_nats[rhs] ) |
||||
return +1 ; |
||||
else if ( roar_nats[lhs] > roar_nats[rhs] ) |
||||
return -1 ; |
||||
else { |
||||
if ( lhs < rhs ) |
||||
return -1 ; |
||||
else if ( lhs > rhs ) |
||||
return +1 ; |
||||
} |
||||
return 0; |
||||
} ) ; |
||||
|
||||
// output the results |
||||
buf.push( "<table>" ) ; |
||||
buf.push( "<tr>", "<th>ROAR nationality", "<th>Count", "<th><tt>vasl-templates</tt> nationality" ) ; |
||||
for ( var i=0 ; i < roar_nats_sorted.length ; ++i ) { |
||||
var nat = roar_nats_sorted[i] ; |
||||
buf.push( "<tr>", "<td>"+nat, "<td>"+roar_nats[nat] ) ; |
||||
nat = convert_roar_nat( nat ) ; |
||||
if ( nat ) |
||||
buf.push( "<td>" + nat ) ; |
||||
} |
||||
buf.push( "</table>" ) ; |
||||
|
||||
// check for spaces in scenario ID's |
||||
buf.push( "<h2>Scenario ID's with spaces</h2>" ) ; |
||||
buf.push( "<ul>" ) ; |
||||
for ( roar_id in gRoarScenarioIndex ) { |
||||
if ( roar_id[0] === "_" ) |
||||
continue ; |
||||
scenario = gRoarScenarioIndex[ roar_id ] ; |
||||
if ( scenario.scenario_id.indexOf( " " ) !== -1 ) |
||||
buf.push( "<li>" + roar_id + ": " + scenario.name + " [" + scenario.scenario_id + "]" ) ; |
||||
} |
||||
buf.push( "</ul>" ) ; |
||||
|
||||
$("#results").html( buf.join("") ).show() ; |
||||
} |
||||
|
||||
</script> |
||||
|
||||
</html> |
@ -0,0 +1,3 @@ |
||||
<div id="select-roar-scenario" style="display:none;"> |
||||
<select></select> |
||||
</div> |
@ -0,0 +1,29 @@ |
||||
{ |
||||
|
||||
"1": { "scenario_id": "TEST 1", "name": "Fighting Withdrawal", |
||||
"publication": "Beyond Valor", |
||||
"results": [ [ "Finnish", 200 ], [ "Russian", 300 ] ], |
||||
"url": "http://test.com/1" |
||||
}, |
||||
|
||||
"2": { "scenario_id": "TEST 2", "name": "Whitewash 1", |
||||
"results": [ [ "American", 10 ], [ "Japanese", 0 ] ], |
||||
"url": "http://test.com/2" |
||||
}, |
||||
|
||||
"3": { "scenario_id": "TEST 3", "name": "Whitewash 2", |
||||
"results": [ [ "American", 0 ], [ "Japanese", 10 ] ], |
||||
"url": "http://test.com/3" |
||||
}, |
||||
|
||||
"4": { "scenario_id": "TEST 4", "name": "No playings", |
||||
"results": [ [ "British", 0 ], [ "French", 0 ] ], |
||||
"url": "http://test.com/4" |
||||
}, |
||||
|
||||
"5": { "scenario_id": "TEST 5", "name": "Unknown nationality", |
||||
"results": [ [ "American", 1 ], [ "Martian", 1 ] ], |
||||
"url": "http://test.com/5" |
||||
} |
||||
|
||||
} |
@ -0,0 +1,148 @@ |
||||
"""Test ROAR integration.""" |
||||
|
||||
import re |
||||
|
||||
from selenium.webdriver.support.ui import Select |
||||
from selenium.webdriver.common.keys import Keys |
||||
|
||||
from vasl_templates.webapp.tests.utils import init_webapp, select_tab, select_menu_option, click_dialog_button, \ |
||||
set_stored_msg_marker, get_stored_msg, set_template_params, add_simple_note, \ |
||||
find_child, find_children, wait_for_elem |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def test_roar( webapp, webdriver ): |
||||
"""Test ROAR integration.""" |
||||
|
||||
# initialize |
||||
init_webapp( webapp, webdriver ) |
||||
|
||||
# check the ROAR info panel |
||||
_check_roar_info( webdriver, None ) |
||||
|
||||
# select a ROAR scenario |
||||
_select_roar_scenario( "fighting withdrawal" ) |
||||
_check_roar_info( webdriver, ( |
||||
( "Fighting Withdrawal", "TEST 1" ), |
||||
( "Finnish", 200, "Russian", 300 ), |
||||
( 40, 60 ) |
||||
) ) |
||||
|
||||
# select some other ROAR scenarios |
||||
# NOTE: The scenario name/ID are already populated, so they don't get updated with the new details. |
||||
_select_roar_scenario( "whitewash 1" ) |
||||
_check_roar_info( webdriver, ( |
||||
( "Fighting Withdrawal", "TEST 1" ), |
||||
( "American", 10, "Japanese", 0 ), |
||||
( 100, 0 ) |
||||
) ) |
||||
_select_roar_scenario( "whitewash 2" ) |
||||
_check_roar_info( webdriver, ( |
||||
( "Fighting Withdrawal", "TEST 1" ), |
||||
( "American", 0, "Japanese", 10 ), |
||||
( 0, 100 ) |
||||
) ) |
||||
|
||||
# unlink from the ROAR scenario |
||||
btn = find_child( "#disconnect-roar" ) |
||||
btn.click() |
||||
_check_roar_info( webdriver, None ) |
||||
|
||||
# select another ROAR scenario (that has no playings) |
||||
set_template_params( { "SCENARIO_NAME": "", "SCENARIO_ID": "" } ) |
||||
_select_roar_scenario( "no playings" ) |
||||
_check_roar_info( webdriver, ( |
||||
( "No playings", "TEST 4" ), |
||||
( "British", 0, "French", 0 ), |
||||
None |
||||
) ) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def test_setting_players( webapp, webdriver ): |
||||
"""Test setting players after selecting a ROAR scenario.""" |
||||
|
||||
# initialize |
||||
init_webapp( webapp, webdriver ) |
||||
|
||||
# select a ROAR scenario |
||||
_select_roar_scenario( "fighting withdrawal" ) |
||||
_check_players( "finnish", "russian" ) |
||||
|
||||
# add something to the Player 1 OB |
||||
select_tab( "ob1" ) |
||||
add_simple_note( find_child("#ob_setups-sortable_1"), "a setup note", None ) |
||||
|
||||
# select another ROAR scenario |
||||
select_tab( "scenario" ) |
||||
_select_roar_scenario( "whitewash 1" ) |
||||
_check_players( "finnish", "japanese" ) # nb: player 1 remains unchanged |
||||
|
||||
# add something to the Player 2 OB |
||||
select_tab( "ob2" ) |
||||
add_simple_note( find_child("#ob_setups-sortable_2"), "another setup note", None ) |
||||
|
||||
# select another ROAR scenario |
||||
select_tab( "scenario" ) |
||||
_select_roar_scenario( "no playings" ) |
||||
_check_players( "finnish", "japanese" ) # nb: both players remain unchanged |
||||
|
||||
# reset the scenario and select a ROAR scenario with an unknown nationality |
||||
select_menu_option( "new_scenario" ) |
||||
click_dialog_button( "OK" ) # nb: dismiss the "discard changes?" prompt |
||||
_ = set_stored_msg_marker( "_last-warning_" ) |
||||
_select_roar_scenario( "unknown nationality" ) |
||||
_check_players( "american", "russian" ) |
||||
last_warning = get_stored_msg( "_last-warning_" ) |
||||
assert re.search( r"Unrecognized nationality.+\bMartian\b", last_warning ) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def _select_roar_scenario( scenario_name ): |
||||
"""Select a ROAR scenario.""" |
||||
btn = find_child( "#search-roar" ) |
||||
btn.click() |
||||
dlg = wait_for_elem( 2, ".ui-dialog.select-roar-scenario" ) |
||||
search_field = find_child( "input", dlg ) |
||||
search_field.send_keys( scenario_name ) |
||||
elems = find_children( ".select2-results li", dlg ) |
||||
assert len(elems) == 1 |
||||
search_field.send_keys( Keys.RETURN ) |
||||
|
||||
def _check_roar_info( webdriver, expected ): |
||||
"""Check the state of the ROAR info panel.""" |
||||
|
||||
# check if the panel is displayed or hidden |
||||
panel = find_child( "#roar-info" ) |
||||
if not expected: |
||||
assert not panel.is_displayed() |
||||
return |
||||
assert panel.is_displayed() |
||||
|
||||
# check the displayed information |
||||
assert find_child( ".name.player1", panel ).text == expected[1][0] |
||||
assert find_child( ".count.player1", panel ).text == "({})".format( expected[1][1] ) |
||||
assert find_child( ".name.player2", panel ).text == expected[1][2] |
||||
assert find_child( ".count.player2", panel ).text == "({})".format( expected[1][3] ) |
||||
|
||||
# check the progress bars |
||||
progress1 = find_child( ".progressbar.player1", panel ) |
||||
progress2 = find_child( ".progressbar.player2", panel ) |
||||
if expected[2]: |
||||
label1 = "{}%".format( expected[2][0] ) |
||||
label2 = "{}%".format( expected[2][1] ) |
||||
expected1, expected2 = 100-expected[2][0], expected[2][1] |
||||
else: |
||||
label1 = label2 = "" |
||||
expected1, expected2 = 100, 0 |
||||
assert find_child( ".label", progress1 ).text == label1 |
||||
assert webdriver.execute_script( "return $(arguments[0]).progressbar('value')", progress1 ) == expected1 |
||||
assert find_child( ".label", progress2 ).text == label2 |
||||
assert webdriver.execute_script( "return $(arguments[0]).progressbar('value')", progress2 ) == expected2 |
||||
|
||||
def _check_players( expected1, expected2 ): |
||||
"""Check the selected players.""" |
||||
sel = Select( find_child( "select[name='PLAYER_1']" ) ) |
||||
assert sel.first_selected_option.get_attribute("value") == expected1 |
||||
sel = Select( find_child( "select[name='PLAYER_2']" ) ) |
||||
assert sel.first_selected_option.get_attribute("value") == expected2 |
Loading…
Reference in new issue