01a166d785268871bae96d0b6059801c3e47a42c
README.md
| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | # SAP Sailing Analytics |
| 2 | + |
|
| 2 | 3 | The SAP Sailing Analytics platform as seen on [sapsailing.com](https://sapsailing.com) |
| 3 | 4 | |
| 4 | 5 | Sailing provides the perfect platform for SAP to showcase solutions and help the sport run like never before. SAP’s involvement in the sport has transformed the sailing experience by providing tools, which: |
build.gradle
| ... | ... | @@ -50,9 +50,9 @@ allprojects { |
| 50 | 50 | ext { |
| 51 | 51 | // regular |
| 52 | 52 | buildTools = "30.0.3" |
| 53 | - compileSdk = 31 |
|
| 53 | + compileSdk = 33 |
|
| 54 | 54 | minSdk = 16 |
| 55 | - targetSdk = 31 |
|
| 55 | + targetSdk = 33 |
|
| 56 | 56 | |
| 57 | 57 | sourceCompatibilityVersion = JavaVersion.VERSION_1_8 |
| 58 | 58 | targetCompatibilityVersion = JavaVersion.VERSION_1_8 |
configuration/httpd/cgi-bin/github.cgi
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | +#!/bin/bash |
|
| 2 | +echo " |
|
| 3 | +" |
|
| 4 | +BODY=$( cat ) |
|
| 5 | +echo "${BODY}" >/tmp/github-hook-body |
|
| 6 | +REF=$( echo "${BODY}" | jq -r '.ref' ) |
|
| 7 | +PUSHER=$( echo "${BODY}" | jq -r '.pusher.email' ) |
|
| 8 | +logger -t github-cgi "ref ia $REF, pusher was $PUSHER" |
|
| 9 | +# For testing: |
|
| 10 | +#if [ "${PUSHER}" = "axel.uhl@sap.com" -a "${REF}" = "refs/heads/translation" ]; then |
|
| 11 | +if [ "${PUSHER}" = "tmsatsls+github.tools.sap_service-tip-git@sap.com" -a "${REF}" = "refs/heads/translation" ]; then |
|
| 12 | + echo "Identified a push to refs/heads/translation by ${PUSHER}." |
|
| 13 | + echo "Fetching translation branch from github.tools.sap and pushing it to ssh://trac@sapsailing.com/home/trac/git" |
|
| 14 | + logger -t github-cgi "fetching translation branch from github.tools.sap and pushing it to ssh://trac@sapsailing.com/home/trac/git" |
|
| 15 | + cd /home/wiki/gitwiki |
|
| 16 | + sudo -u wiki git fetch sapsailing translation:translation 2>&1 |
|
| 17 | + sudo -u wiki git push origin translation:translation 2>&1 |
|
| 18 | +else |
|
| 19 | + echo "Either pusher was not tmsatsls+github.tools.sap_service-tip-git@sap.com or ref was not refs/heads/translation. Not triggering." |
|
| 20 | +fi |
configuration/httpd/conf.d/000-macros.conf
| ... | ... | @@ -1,57 +1,9 @@ |
| 1 | 1 | # ARCHIVE, based on i3.2xlarge, 64GB RAM and 1.9TB swap |
| 2 | -#Define ARCHIVE_IP 172.31.24.90 |
|
| 3 | -#Define ARCHIVE_IP 172.31.24.90 |
|
| 4 | -#Define ARCHIVE_IP 172.31.20.25 |
|
| 5 | -#Define ARCHIVE_IP 172.31.16.178 |
|
| 6 | -#Define ARCHIVE_IP 172.31.25.130 |
|
| 7 | -#Define ARCHIVE_IP 172.31.25.246 |
|
| 8 | -#Define ARCHIVE_IP 172.31.24.192 |
|
| 9 | -#Define ARCHIVE_IP 172.31.22.16 |
|
| 10 | -# Failover (hanging in full gc): |
|
| 11 | -#Define ARCHIVE_IP 172.31.29.138 |
|
| 12 | -# Production (hanging in full gc): |
|
| 13 | -#Define ARCHIVE_IP 172.31.18.201 |
|
| 14 | -# New Candidate, not really ready... |
|
| 15 | -#Define ARCHIVE_IP 172.31.28.255 |
|
| 16 | -#Define ARCHIVE_IP 172.31.21.159 |
|
| 17 | -#Define ARCHIVE_IP 172.31.19.41 |
|
| 18 | -#Define ARCHIVE_IP 172.31.29.206 |
|
| 19 | -#Define ARCHIVE_IP 172.31.25.194 |
|
| 20 | -#Define ARCHIVE_IP 172.31.17.130 |
|
| 21 | -#Define ARCHIVE_IP 172.31.20.156 |
|
| 22 | -#Define ARCHIVE_IP 172.31.28.168 |
|
| 23 | -#Define ARCHIVE_IP 172.31.23.135 |
|
| 24 | -#Define ARCHIVE_IP 172.31.16.245 |
|
| 25 | -#Define ARCHIVE_IP 172.31.29.3 |
|
| 26 | -#Define ARCHIVE_IP 172.31.20.157 |
|
| 27 | -#Define ARCHIVE_IP 172.31.17.35 |
|
| 28 | -#Define ARCHIVE_IP 172.31.28.236 |
|
| 29 | -#Define ARCHIVE_IP 172.31.16.207 |
|
| 30 | -#Define ARCHIVE_IP 172.31.26.1 |
|
| 31 | -#Define ARCHIVE_IP 172.31.21.3 |
|
| 32 | -#Define ARCHIVE_IP 172.31.28.73 |
|
| 33 | -#Define ARCHIVE_IP 172.31.17.113 |
|
| 34 | -#Define ARCHIVE_IP 172.31.30.211 |
|
| 35 | -#Define ARCHIVE_IP 172.31.20.222 |
|
| 36 | -#Define ARCHIVE_IP 172.31.21.139 |
|
| 37 | -#Define ARCHIVE_IP 172.31.23.47 |
|
| 38 | -#Define ARCHIVE_IP 172.31.20.62 |
|
| 39 | -#Define ARCHIVE_IP 172.31.20.131 |
|
| 40 | -#Define ARCHIVE_IP 172.31.47.208 |
|
| 41 | -#Define ARCHIVE_IP 172.31.41.14 |
|
| 42 | -#Define ARCHIVE_IP 172.31.34.59 |
|
| 43 | -#Define ARCHIVE_IP 172.31.46.41 |
|
| 44 | -#Define ARCHIVE_IP 172.31.44.232 |
|
| 45 | -#Define ARCHIVE_IP 172.31.46.28 |
|
| 46 | -#Define ARCHIVE_IP 172.31.45.169 |
|
| 47 | -#Define ARCHIVE_IP 172.31.45.31 |
|
| 48 | -#Define ARCHIVE_IP 172.31.44.175 |
|
| 49 | 2 | #Define ARCHIVE_IP 172.31.36.140 |
| 50 | 3 | Define ARCHIVE_IP 172.31.41.241 |
| 51 | 4 | Define ARCHIVE_FAILOVER_IP 172.31.47.152 |
| 52 | 5 | Define PRODUCTION |
| 53 | 6 | |
| 54 | - |
|
| 55 | 7 | <Macro ArchiveRewrite> |
| 56 | 8 | Use Rewrite ${PRODUCTION} 8888 |
| 57 | 9 | </Macro> |
configuration/httpd/conf.d/001-events.conf
| ... | ... | @@ -21,8 +21,8 @@ Use Status 172.31.42.171 internal-server-status |
| 21 | 21 | Use Status 127.0.0.1 internal-server-status |
| 22 | 22 | |
| 23 | 23 | # Uncomment the following and update the internal IP to what your Archive server candidate is |
| 24 | -Use Plain archive-failover.sapsailing.com 172.31.44.175 8888 |
|
| 25 | -Use Plain archive-candidate.sapsailing.com 172.31.46.176 8888 |
|
| 24 | +Use Plain archive-failover.sapsailing.com 172.31.40.12 8888 |
|
| 25 | +Use Plain archive-candidate.sapsailing.com 172.31.46.203 8888 |
|
| 26 | 26 | |
| 27 | 27 | Use Plain my-master.sapsailing.com 172.31.24.34 8888 |
| 28 | 28 |
configuration/httpd/conf.d/004-git-ssl.conf
| ... | ... | @@ -3,6 +3,7 @@ ServerName git.sapsailing.com |
| 3 | 3 | SetEnv GIT_PROJECT_ROOT /home/trac/git |
| 4 | 4 | SetEnv GIT_HTTP_EXPORT_ALL |
| 5 | 5 | ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ |
| 6 | +ScriptAlias /hooks/ /var/www/cgi-bin/ |
|
| 6 | 7 | |
| 7 | 8 | <Location /git/ > |
| 8 | 9 | Options +ExecCGI |
| ... | ... | @@ -127,6 +128,10 @@ SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt |
| 127 | 128 | |
| 128 | 129 | <Directory "/var/www/cgi-bin"> |
| 129 | 130 | SSLOptions +StdEnvVars |
| 131 | + AllowOverride None |
|
| 132 | + Options FollowSymLinks |
|
| 133 | + Order allow,deny |
|
| 134 | + Allow from all |
|
| 130 | 135 | </Directory> |
| 131 | 136 | |
| 132 | 137 | # SSL Protocol Adjustments: |
configuration/httpd/conf.d/49erEuros2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Event-ARCHIVE 49erEuros2023.sapsailing.com ebe42774-76e1-4f9d-8e3a-829cc263088e |
configuration/httpd/conf.d/bundesliga2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Home-ARCHIVE bundesliga2023.sapsailing.com |
configuration/httpd/conf.d/danishleague2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Home-ARCHIVE danishleague2023.sapsailing.com |
configuration/httpd/conf.d/finalregionalny2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Home-ARCHIVE finalregionalny2023.sapsailing.com |
configuration/httpd/conf.d/frenchleague2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Event-ARCHIVE frenchleague2023.sapsailing.com 289ee506-e8de-42e3-b726-f339fdba3b31 |
configuration/httpd/conf.d/norwegianleague2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Home-ARCHIVE norwegianleague2023.sapsailing.com |
configuration/httpd/conf.d/scl2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Home-ARCHIVE scl2023.sapsailing.com |
configuration/httpd/conf.d/switchoverArchive.sh
| ... | ... | @@ -26,7 +26,7 @@ setProductionMainIfNotSet() { |
| 26 | 26 | #currently unhealthy |
| 27 | 27 | #set production to archive |
| 28 | 28 | logger -t archive "Healthy: setting production to main archive" |
| 29 | - sed -i -E "s/Define PRODUCTION .*/Define PRODUCTION \${ARCHIVE_IP}/" ${MACROS_PATH} |
|
| 29 | + sed -i -E "s/^Define PRODUCTION .*/Define PRODUCTION \${ARCHIVE_IP}/" ${MACROS_PATH} |
|
| 30 | 30 | notify "Healthy: main archive online" |
| 31 | 31 | else |
| 32 | 32 | #If already healthy then no reload or notification occurs. |
| ... | ... | @@ -46,7 +46,7 @@ then |
| 46 | 46 | then |
| 47 | 47 | #set production to failover if not already. Separate if statement in case the curl statement |
| 48 | 48 | #fails but the production is already set to point to the backup |
| 49 | - sed -i -E "s/Define PRODUCTION .*/Define PRODUCTION \${ARCHIVE_FAILOVER_IP}/" ${MACROS_PATH} |
|
| 49 | + sed -i -E "s/^Define PRODUCTION .*/Define PRODUCTION \${ARCHIVE_FAILOVER_IP}/" ${MACROS_PATH} |
|
| 50 | 50 | logger -t archive "Unhealthy: second check failed, switching to failover" |
| 51 | 51 | systemctl reload httpd |
| 52 | 52 | notify-operators "Unhealthy: main archive offline" |
configuration/httpd/conf.d/switchoverArchive.sh.orig
| ... | ... | @@ -0,0 +1,72 @@ |
| 1 | +#!/bin/bash |
|
| 2 | +MACROS_PATH=$1 |
|
| 3 | +TIMEOUT1=2 |
|
| 4 | +TIMEOUT2=9 |
|
| 5 | +#Purpose: Script is used to switch to the failover archive if the primary is unhealthy by altering the macros |
|
| 6 | +#definitions and then reloading. |
|
| 7 | + |
|
| 8 | +#These next lines get the current ip values for the archive and failover, plus they store the value of production, |
|
| 9 | +#which is a variable pointing to either the primary or failover value. |
|
| 10 | +archiveIp="$(sed -n -E 's/^Define ARCHIVE_IP (.*)/\1/p' ${MACROS_PATH} | tr -d '[:space:]')" |
|
| 11 | +failoverIp="$(sed -n -E 's/^Define ARCHIVE_FAILOVER_IP (.*)/\1/p' ${MACROS_PATH} | tr -d '[:space:]')" |
|
| 12 | +productionIp="$(sed -n -E 's/^Define PRODUCTION (.*)/\1/p' ${MACROS_PATH} | tr -d '[:space:]')" |
|
| 13 | +#Checks if the macro.conf is set as healthy or unhealthy currently. |
|
| 14 | +if [[ "${productionIp}" == "\${ARCHIVE_IP}" ]] |
|
| 15 | +then |
|
| 16 | + alreadyHealthy=1 |
|
| 17 | + logger -t archive "currently healthy" |
|
| 18 | +else |
|
| 19 | + alreadyHealthy=0 |
|
| 20 | + logger -t archive "currently unhealthy" |
|
| 21 | +fi |
|
| 22 | +#Sets the production value to point to the variable defining the main archive IP, provided it isn't already set. |
|
| 23 | +setProductionMainIfNotSet() { |
|
| 24 | + if [[ $alreadyHealthy -eq 0 ]] |
|
| 25 | + then |
|
| 26 | + #currently unhealthy |
|
| 27 | + #set production to archive |
|
| 28 | + logger -t archive "Healthy: setting production to main archive" |
|
| 29 | +<<<<<<< HEAD |
|
| 30 | + sed -i -E "s/Define PRODUCTION .*/Define PRODUCTION \${ARCHIVE_IP}/" ${MACROS_PATH} |
|
| 31 | + notify "Healthy: main archive online" |
|
| 32 | +======= |
|
| 33 | + sed -i -E "s/^Define PRODUCTION .*/Define PRODUCTION \${ARCHIVE_IP}/" ${MACROS_PATH} |
|
| 34 | + systemctl reload httpd |
|
| 35 | + { |
|
| 36 | + echo "To: ${EMAIL}" |
|
| 37 | + echo Subject: Healthy |
|
| 38 | + echo |
|
| 39 | + echo Healthy: main archive online |
|
| 40 | + } | /usr/sbin/sendmail -t |
|
| 41 | +>>>>>>> ec87e54291196f3ae8381b9b5df50c24b264238f |
|
| 42 | + else |
|
| 43 | + #If already healthy then no reload or notification occurs. |
|
| 44 | + logger -t archive "Healthy: already set, no change needed" |
|
| 45 | + fi |
|
| 46 | + |
|
| 47 | +} |
|
| 48 | +logger -t archive "begin check" |
|
| 49 | +curl -s --connect-timeout ${TIMEOUT1} "http://${archiveIp}:8888/gwt/status" >> /dev/null |
|
| 50 | +if [[ $? -ne 0 ]] |
|
| 51 | +then |
|
| 52 | + logger -t archive "first check failed" |
|
| 53 | + curl -s --connect-timeout ${TIMEOUT2} "http://${archiveIp}:8888/gwt/status" >> /dev/null |
|
| 54 | + if [[ $? -ne 0 ]] |
|
| 55 | + then |
|
| 56 | + if [[ $alreadyHealthy -eq 1 ]] |
|
| 57 | + then |
|
| 58 | + #set production to failover if not already. Separate if statement in case the curl statement |
|
| 59 | + #fails but the production is already set to point to the backup |
|
| 60 | + sed -i -E "s/^Define PRODUCTION .*/Define PRODUCTION \${ARCHIVE_FAILOVER_IP}/" ${MACROS_PATH} |
|
| 61 | + logger -t archive "Unhealthy: second check failed, switching to failover" |
|
| 62 | + systemctl reload httpd |
|
| 63 | + notify-operators "Unhealthy: main archive offline" |
|
| 64 | + else |
|
| 65 | + logger -t archive "Unhealthy: second check still fails, failover already in use" |
|
| 66 | + fi |
|
| 67 | + else |
|
| 68 | + setProductionMainIfNotSet |
|
| 69 | + fi |
|
| 70 | +else |
|
| 71 | + setProductionMainIfNotSet |
|
| 72 | +fi |
configuration/httpd/conf.d/trojmiejskaliga2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Home-ARCHIVE trojmiejskaliga2023.sapsailing.com |
configuration/httpd/conf.d/wordpress.conf.bak
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +# The problem with this is that Wordpress seems to redirect to https, |
|
| 2 | +# leading to an endless redirect loop when the ALB then receives the |
|
| 3 | +# https request, does the SSL handling and forwards as an http request |
|
| 4 | +# to here again... |
|
| 5 | +<VirtualHost *:80> |
|
| 6 | + ServerName blog.sapsailing.com |
|
| 7 | + CustomLog logs/blog_log combined |
|
| 8 | + DocumentRoot /home/wordpress/wordpress |
|
| 9 | + <Directory /home/wordpress/wordpress> |
|
| 10 | + Options Indexes FollowSymLinks MultiViews |
|
| 11 | + AllowOverride All |
|
| 12 | + Order allow,deny |
|
| 13 | + allow from all |
|
| 14 | + </Directory> |
|
| 15 | +</VirtualHost> |
|
| 16 | + |
configuration/httpd/conf.d/wow2023.sapsailing.com.conf
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Use Home-ARCHIVE wow2023.sapsailing.com |
configuration/imageupgrade_functions.sh
| ... | ... | @@ -55,12 +55,12 @@ clean_httpd_logs() { |
| 55 | 55 | echo "Clearing httpd logs" >>/var/log/sailing.err |
| 56 | 56 | service httpd stop |
| 57 | 57 | rm -rf /var/log/httpd/* |
| 58 | - rm /etc/httpd/conf.d/001-internals.conf |
|
| 58 | + rm -f /etc/httpd/conf.d/001-internals.conf |
|
| 59 | 59 | } |
| 60 | 60 | |
| 61 | 61 | clean_startup_logs() { |
| 62 | 62 | echo "Clearing bootstrap logs" >>/var/log/sailing.err |
| 63 | - rm /var/log/sailing* |
|
| 63 | + rm -f /var/log/sailing* |
|
| 64 | 64 | # Ensure that upon the next boot the reboot indicator is not present, indicating that it's the first boot |
| 65 | 65 | rm "${REBOOT_INDICATOR}" |
| 66 | 66 | } |
| ... | ... | @@ -95,7 +95,7 @@ finalize() { |
| 95 | 95 | else |
| 96 | 96 | # Only clean ${LOGON_USER_HOME}/.ssh directory and /tmp/image-upgrade-finished if the next step is shutdown / image creation |
| 97 | 97 | clean_root_ssh_dir_and_tmp |
| 98 | - rm /var/log/sailing.err |
|
| 98 | + rm -f /var/log/sailing.err |
|
| 99 | 99 | shutdown -h now & |
| 100 | 100 | fi |
| 101 | 101 | } |
configuration/tractrac-json-urls
| ... | ... | @@ -392,15 +392,45 @@ https://event3.tractrac.com/events/event_20221211_TestOceanR/jsonservice.php |
| 392 | 392 | https://event3.tractrac.com/events/event_20230223_TheOceanRa/jsonservice.php |
| 393 | 393 | https://event3.tractrac.com/events/event_20230328_TrofeoSARP/jsonservice.php |
| 394 | 394 | https://event3.tractrac.com/events/event_20230329_AllianzReg/jsonservice.php |
| 395 | +https://event3.tractrac.com/events/event_20230329_SailingCha1/jsonservice.php |
|
| 395 | 396 | https://event3.tractrac.com/events/event_20230420_TheOceanRa/jsonservice.php |
| 396 | 397 | https://event3.tractrac.com/events/event_20230421_FrenchOlym/jsonservice.php |
| 398 | +https://event3.tractrac.com/events/event_20230427_SailingCha1/jsonservice.php |
|
| 399 | +https://event3.tractrac.com/events/event_20230505_NorskSeils/jsonservice.php |
|
| 400 | +https://event3.tractrac.com/events/event_20230511_SegelBunde/jsonservice.php |
|
| 401 | +https://event3.tractrac.com/events/event_20230517_Sejlsports/jsonservice.php |
|
| 402 | +https://event3.tractrac.com/events/event_20230520_Sejlsports/jsonservice.php |
|
| 397 | 403 | https://event3.tractrac.com/events/event_20230603_TheOceanRa/jsonservice.php |
| 398 | 404 | https://event3.tractrac.com/events/event_20230607_HelgaCup/jsonservice.php |
| 405 | +https://event3.tractrac.com/events/event_20230609_Sejlsports1/jsonservice.php |
|
| 406 | +https://event3.tractrac.com/events/event_20230614_SailingCha1/jsonservice.php |
|
| 399 | 407 | https://event3.tractrac.com/events/event_20230614_TheOceanRa/jsonservice.php |
| 408 | +https://event3.tractrac.com/events/event_20230616_Sejlsports/jsonservice.php |
|
| 409 | +https://event3.tractrac.com/events/event_20230621_NorskSeils/jsonservice.php |
|
| 400 | 410 | https://event3.tractrac.com/events/event_20230629_TheOceanRa/jsonservice.php |
| 401 | 411 | https://event3.tractrac.com/events/event_20230706_ParisOlymp/jsonservice.php |
| 412 | +https://event3.tractrac.com/events/event_20230721_SailingCha1/jsonservice.php |
|
| 413 | +https://event3.tractrac.com/events/event_20230727_SegelBunde/jsonservice.php |
|
| 414 | +https://event3.tractrac.com/events/event_20230801_NorskSeils/jsonservice.php |
|
| 402 | 415 | https://event3.tractrac.com/events/event_20230809_AllianzPar/jsonservice.php |
| 403 | 416 | https://event3.tractrac.com/events/event_20230809_AllianzSai1/jsonservice.php |
| 417 | +https://event3.tractrac.com/events/event_20230812_CopyofSejl/jsonservice.php |
|
| 418 | +https://event3.tractrac.com/events/event_20230812_Sejlsports/jsonservice.php |
|
| 419 | +https://event3.tractrac.com/events/event_20230818_SegelBunde/jsonservice.php |
|
| 420 | +https://event3.tractrac.com/events/event_20230818_Ungdomslig/jsonservice.php |
|
| 421 | +https://event3.tractrac.com/events/event_20230826_Sejlsports/jsonservice.php |
|
| 422 | +https://event3.tractrac.com/events/event_20230831_SailingCha1/jsonservice.php |
|
| 423 | +https://event3.tractrac.com/events/event_20230902_Sejlsports/jsonservice.php |
|
| 424 | +https://event3.tractrac.com/events/event_20230913_SailingCha1/jsonservice.php |
|
| 425 | +https://event3.tractrac.com/events/event_20230914_SegelBunde/jsonservice.php |
|
| 426 | +https://event3.tractrac.com/events/event_20230922_NorskSeils/jsonservice.php |
|
| 427 | +https://event3.tractrac.com/events/event_20230923_XYachtsSta/jsonservice.php |
|
| 428 | +https://event3.tractrac.com/events/event_20230930_Ungdomslig/jsonservice.php |
|
| 429 | +https://event3.tractrac.com/events/event_20231005_CopyofSege/jsonservice.php |
|
| 430 | +https://event3.tractrac.com/events/event_20231011_LigueNatio/jsonservice.php |
|
| 431 | +https://event3.tractrac.com/events/event_20231012_MesternesM/jsonservice.php |
|
| 432 | +https://event3.tractrac.com/events/event_20231018_SegelBunde/jsonservice.php |
|
| 433 | +https://event3.tractrac.com/events/event_20231105_Europeaner/jsonservice.php |
|
| 404 | 434 | https://event.tractrac.com/events/event_20170727_Travemnder2/jsonservice.php |
| 405 | 435 | https://event.tractrac.com/events/event_20171013_DSLPokalGl/jsonservice.php |
| 406 | 436 | https://event.tractrac.com/events/event_20181012_MstarnasMs/jsonservice.php |
| ... | ... | @@ -475,6 +505,7 @@ https://event.tractrac.com/events/event_20211017_KornatiCup/jsonservice.php |
| 475 | 505 | https://event.tractrac.com/events/event_20211115_ererFxNacr/jsonservice.php |
| 476 | 506 | https://event.tractrac.com/events/event_20211210_YouthSaili/jsonservice.php |
| 477 | 507 | https://event.tractrac.com/events/event_20220626_KielerWoch/jsonservice.php |
| 508 | +https://event.tractrac.com/events/event_20230324_SAILINGCha/jsonservice.php |
|
| 478 | 509 | https://event.tractrac.com/events/event_20230531_ERERFXNACR/jsonservice.php |
| 479 | 510 | https://event.tractrac.com/events/event_20230611_KielerWoch/jsonservice.php |
| 480 | 511 | https://event.tractrac.com/events/event_20230622_KielerWoch/jsonservice.php |
docker/docker-compose.yml
| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | version: "3.9" |
| 2 | 2 | services: |
| 3 | 3 | sailing-analytics: |
| 4 | - image: "docker.sapsailing.com:443/sapsailing:latest" |
|
| 4 | + image: "docker.sapsailing.com/sapsailing:latest" |
|
| 5 | 5 | ports: |
| 6 | 6 | - "8888:8888" |
| 7 | 7 | - "6666:6666" |
home.md
| ... | ... | @@ -26,6 +26,7 @@ SAP is at the center of today’s technology revolution, developing innovations |
| 26 | 26 | * [[User Management|wiki/info/landscape/usermanagement]] |
| 27 | 27 | * [[Development Environment|wiki/info/landscape/development-environment]] |
| 28 | 28 | * [[Production Environment|wiki/info/landscape/production-environment]] |
| 29 | + * [[Internationalization (i18n)|wiki/howto/development/i18n]] |
|
| 29 | 30 | * [[Typical Development Scenarios|wiki/info/landscape/typical-development-scenarios]] |
| 30 | 31 | * [[RaceLog Tracking Server Architecture|wiki/info/landscape/server]] |
| 31 | 32 | * Environment Overview [[PDF|wiki/info/mobile/event-tracking/architecture.pdf]] | [[SVG|wiki/info/mobile/event-tracking/architecture.svg]] |
java/com.sap.sailing.competitorimport/src/com/sap/sailing/competitorimport/CompetitorProvider.java
| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | package com.sap.sailing.competitorimport; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | +import java.net.URISyntaxException; |
|
| 4 | 5 | import java.util.Locale; |
| 5 | 6 | import java.util.Map; |
| 6 | 7 | import java.util.Set; |
| ... | ... | @@ -32,7 +33,7 @@ public interface CompetitorProvider extends Named { |
| 32 | 33 | * competitor names. Should the value for a key be <code>null</code>, competitors for the event may still |
| 33 | 34 | * be available, only they may not be keyed per regatta in that case. |
| 34 | 35 | */ |
| 35 | - Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException; |
|
| 36 | + Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException, URISyntaxException; |
|
| 36 | 37 | |
| 37 | 38 | /** |
| 38 | 39 | * Obtains competitor records from the source of import |
| ... | ... | @@ -44,7 +45,7 @@ public interface CompetitorProvider extends Named { |
| 44 | 45 | * <code>eventName</code>, or a regatta name as provided in the value set for the key |
| 45 | 46 | * <code>eventName</code> as returned by {@link #getHasCompetitorsForRegattasInEvent()}. |
| 46 | 47 | */ |
| 47 | - Iterable<CompetitorDescriptor> getCompetitorDescriptors(String eventName, String regattaName) throws JAXBException, IOException; |
|
| 48 | + Iterable<CompetitorDescriptor> getCompetitorDescriptors(String eventName, String regattaName) throws JAXBException, IOException, URISyntaxException; |
|
| 48 | 49 | |
| 49 | 50 | /** |
| 50 | 51 | * A hint for users about this competitor provider; ideally mapped to the user's locale with i18n support. |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Team |
| 24 | 24 | SearchTag=Search Tag |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Leaderboards |
| 26 | 26 | RaceSailingDomainRetrieverChain=Races |
| 27 | -LegSailingDomainRetrieverChain=Legs |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Legs of Competitors |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=GPS Fixes |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Bravo Fixes |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Bravo Tracks |
| ... | ... | @@ -254,4 +254,6 @@ CompetitorDaySailingDomainRetrieverChain=Competitor-Day for race |
| 254 | 254 | LowestSpeed=Lowest Speed |
| 255 | 255 | SpeedChange=Speed Change |
| 256 | 256 | MaximumTurningRate=Maximum turning rate (°/s) |
| 257 | -AverageTurningRate=Average turning rate (°/s) |
|
| ... | ... | \ No newline at end of file |
| 0 | +AverageTurningRate=Average turning rate (°/s) |
|
| 1 | +LengthOfTheLeg=Length of the Leg |
|
| 2 | +LegSailingDomainRetrieverChain=Leg |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_cs.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Tým |
| 24 | 24 | SearchTag=Hledací znak |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Výsledkové tabule |
| 26 | 26 | RaceSailingDomainRetrieverChain=Rozjížďky |
| 27 | -LegSailingDomainRetrieverChain=Úseky |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Úseky závodníků |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=Záznamy polohy GPS |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Záznamy polohy Bravo |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Dráhy Bravo |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Nejnižší rychlost |
| 255 | 255 | SpeedChange=Změna rychlosti |
| 256 | 256 | MaximumTurningRate=Maximální tempo změny kurzu (°/s) |
| 257 | 257 | AverageTurningRate=Průměrné tempo změny kurzu (°/s) |
| 258 | +LengthOfTheLeg=Délka úseku |
|
| 259 | +LegSailingDomainRetrieverChain=Úsek |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_da.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Team |
| 24 | 24 | SearchTag=Søgetag |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Ranglister |
| 26 | 26 | RaceSailingDomainRetrieverChain=Sejladser |
| 27 | -LegSailingDomainRetrieverChain=Ben |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Ben for konkurrenter |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=GPS-registreringer |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Bravo-registreringer |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Bravo-spor |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Laveste hastighed |
| 255 | 255 | SpeedChange=Hastighedsændring |
| 256 | 256 | MaximumTurningRate=Maks. drejehastighed (°/s) |
| 257 | 257 | AverageTurningRate=Gennemsnitlig drejehastighed (°/s) |
| 258 | +LengthOfTheLeg=Længde på benet |
|
| 259 | +LegSailingDomainRetrieverChain=Ben |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties
| ... | ... | @@ -10,8 +10,8 @@ Acronym=Abkürzung |
| 10 | 10 | SailID=Segelnummer |
| 11 | 11 | BoatName=Bootsname |
| 12 | 12 | Color=Farbe |
| 13 | -LegNumber=Nummer des Schenkels |
|
| 14 | -LegType=Art des Schenkels |
|
| 13 | +LegNumber=Nummer des Bahnschenkels |
|
| 14 | +LegType=Art des Bahnschenkels |
|
| 15 | 15 | Year=Jahr |
| 16 | 16 | Regatta=Regatta |
| 17 | 17 | CourseArea=Regattabahn |
| ... | ... | @@ -21,7 +21,7 @@ RaceColumn=Rennspalte |
| 21 | 21 | Race=Rennen |
| 22 | 22 | LeaderboardSailingDomainRetrieverChain=Leaderboards |
| 23 | 23 | RaceSailingDomainRetrieverChain=Rennen |
| 24 | -LegSailingDomainRetrieverChain=Schenkel |
|
| 24 | +LegOfCompetitorSailingDomainRetrieverChain=Bahnschenkel eines Teilnehmers |
|
| 25 | 25 | GPSFixSailingDomainRetrieverChain=GPS-Fixes |
| 26 | 26 | BravoFixSailingDomainRetrieverChain=Bravo-Fixes |
| 27 | 27 | BravoFixTrackSailingDomainRetrieverChain=Bravo Messreihen |
| ... | ... | @@ -35,9 +35,9 @@ RaceOfCompetitorSailingDomainRetrieverChain=Rennen eines Teilnehmers |
| 35 | 35 | ManeuverSailingDomainRetrieverChain=Manöver |
| 36 | 36 | LeaderboardGroup=Ranglisten Gruppe |
| 37 | 37 | Leaderboard=Rangliste |
| 38 | -Leg=Schenkel |
|
| 38 | +Leg=Bahnschenkel |
|
| 39 | 39 | MarkPassing=Tonnenrundung |
| 40 | -LegOfCompetitor=Schenkel eines Teilnehmers |
|
| 40 | +LegOfCompetitor=Bahnschenkel eines Teilnehmers |
|
| 41 | 41 | GpsFix=GPS-Fix |
| 42 | 42 | BravoFix=Bravo-Fix |
| 43 | 43 | LineLengthAtStart=Länge der Startlinie zum Startzeitpunkt |
| ... | ... | @@ -117,7 +117,7 @@ RankThirtySecondsAfterStart=Absolute Platzierung 30s nach dem Startzeitpunkt |
| 117 | 117 | WindwardDistanceToAdvantageousEndOfLineAtStartOfRace=Windwärtige Distanz zum Bevorteilten Ende der Startlinie zum Start des Rennens |
| 118 | 118 | WindwardDistanceToAdvantageousEndOfLineAtStartOfCompetitor=Windwärtige Distanz zum Bevorteilten Ende der Startlinie zum Start des Teilnehmers |
| 119 | 119 | AbsoluteWindwardDistanceToStarboardSideAtStartOfCompetitor=Absolute Windwärtige Distanz zur Steuerbordseite beim Start des Teilnehmers |
| 120 | -RankAfterHalfOfTheFirstLeg=Platzierung nach der Hälfte des ersten Schenkels |
|
| 120 | +RankAfterHalfOfTheFirstLeg=Platzierung nach der Hälfte des ersten Bahnschenkels |
|
| 121 | 121 | AdvantageousEndOfLine=Bevorteiltes Ende der Startlinie |
| 122 | 122 | AdvantageOfStarboardSideOfStartLine=Vorteil der Steuerbord-Seite der Startlinie |
| 123 | 123 | TrueWindAngleOfStartLineSeenFromStarboardSide=Winkel der Startlinie zum wahren Wind vom Steuerbord-Ende aus gesehen |
| ... | ... | @@ -257,4 +257,6 @@ CompetitorDaySailingDomainRetrieverChain=Teilnehmer-Tag für Wettfahrt |
| 257 | 257 | LowestSpeed=Niedrigste Geschwindigkeit |
| 258 | 258 | SpeedChange=Geschwindigkeits-Änderung |
| 259 | 259 | MaximumTurningRate=Maximale Drehgeschwindigkeit (°/s) |
| 260 | -AverageTurningRate=Durchschnittliche Drehgeschwindigkeit (°/s) |
|
| ... | ... | \ No newline at end of file |
| 0 | +AverageTurningRate=Durchschnittliche Drehgeschwindigkeit (°/s) |
|
| 1 | +LengthOfTheLeg=Länge eines Bahnschenkels |
|
| 2 | +LegSailingDomainRetrieverChain=Bahnschenkel |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Equipo |
| 24 | 24 | SearchTag=Etiqueta de búsqueda |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Clasificaciones |
| 26 | 26 | RaceSailingDomainRetrieverChain=Carreras |
| 27 | -LegSailingDomainRetrieverChain=Tramos |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Tramos de competidores |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=Puntos de GPS |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Puntos de Bravo |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Seguimientos Bravo |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Velocidad más baja |
| 255 | 255 | SpeedChange=Cambio de velocidad |
| 256 | 256 | MaximumTurningRate=Velocidad máxima de giro (°/s) |
| 257 | 257 | AverageTurningRate=Velocidad media de giro (°/s) |
| 258 | +LengthOfTheLeg=Longitud del tramo |
|
| 259 | +LegSailingDomainRetrieverChain=Tramo |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Équipe |
| 24 | 24 | SearchTag=Balise de recherche |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Palmarès |
| 26 | 26 | RaceSailingDomainRetrieverChain=Courses |
| 27 | -LegSailingDomainRetrieverChain=Portions de parcours |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Portions de parcours des concurrents |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=Positions GPS |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Positions Bravo |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Enregistrements Bravo |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Vitesse la plus basse |
| 255 | 255 | SpeedChange=Changement de vitesse |
| 256 | 256 | MaximumTurningRate=Vitesse angulaire maximale (°/s) |
| 257 | 257 | AverageTurningRate=Vitesse angulaire moyenne (°/s) |
| 258 | +LengthOfTheLeg=Longueur de la portion de parcours |
|
| 259 | +LegSailingDomainRetrieverChain=Portion de parcours |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_it.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Squadra |
| 24 | 24 | SearchTag=Tag di ricerca |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Classifiche |
| 26 | 26 | RaceSailingDomainRetrieverChain=Gare |
| 27 | -LegSailingDomainRetrieverChain=Tratte |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Tratte dei concorrenti |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=Punti nave GPS |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Punti nave Bravo |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Tracce Bravo |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Velocità più bassa |
| 255 | 255 | SpeedChange=Cambio di velocità |
| 256 | 256 | MaximumTurningRate=Velocità di virata massima (°/s) |
| 257 | 257 | AverageTurningRate=Velocità di virata media (°/s) |
| 258 | +LengthOfTheLeg=Lunghezza della tratta |
|
| 259 | +LegSailingDomainRetrieverChain=Tratta |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=チーム |
| 24 | 24 | SearchTag=検索タグ |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=リーダーボード |
| 26 | 26 | RaceSailingDomainRetrieverChain=レース |
| 27 | -LegSailingDomainRetrieverChain=レグ |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=競技者のレグ |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=GPS フィックス |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Bravo フィックス |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Bravo トラック |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=最低速度 |
| 255 | 255 | SpeedChange=速度変更 |
| 256 | 256 | MaximumTurningRate=最大回転率 (°/s) |
| 257 | 257 | AverageTurningRate=平均回転率 (°/s) |
| 258 | +LengthOfTheLeg=レグの長さ |
|
| 259 | +LegSailingDomainRetrieverChain=レグ |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Equipe |
| 24 | 24 | SearchTag=Etiqueta de pesquisa |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Painéis de classificação |
| 26 | 26 | RaceSailingDomainRetrieverChain=Corridas |
| 27 | -LegSailingDomainRetrieverChain=Pernas |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Pernas de competidores |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=Pontos fixos de GPS |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Pontos fixos Bravo |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Pistas Bravo |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Velocidade mais baixa |
| 255 | 255 | SpeedChange=Mudança de velocidade |
| 256 | 256 | MaximumTurningRate=Taxa de viragem máxima (°/s) |
| 257 | 257 | AverageTurningRate=Taxa de viragem média (°/s) |
| 258 | +LengthOfTheLeg=Comprimento da perna |
|
| 259 | +LegSailingDomainRetrieverChain=Perna |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Команда |
| 24 | 24 | SearchTag=Тег поиска |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Таблицы лидеров |
| 26 | 26 | RaceSailingDomainRetrieverChain=Гонки |
| 27 | -LegSailingDomainRetrieverChain=Отрезки |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Отрезки участников |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=Замеры GPS |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Замеры Bravo |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Отслеживания Bravo |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Наименьшая скорость |
| 255 | 255 | SpeedChange=Изменение скорости |
| 256 | 256 | MaximumTurningRate=Максимальная скорость поворота (°/c) |
| 257 | 257 | AverageTurningRate=Средняя скорость поворота (°/c) |
| 258 | +LengthOfTheLeg=Длина отрезка |
|
| 259 | +LegSailingDomainRetrieverChain=Отрезок |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_sl.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=Ekipa |
| 24 | 24 | SearchTag=Oznaka iskanja |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=Lestvice vodilnih |
| 26 | 26 | RaceSailingDomainRetrieverChain=Plovi |
| 27 | -LegSailingDomainRetrieverChain=Stranice |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=Stranice tekmovalcev |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=GPS-položaj |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Bravo-položaji |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Bravo-sledi |
| ... | ... | @@ -255,3 +255,5 @@ LowestSpeed=Najnižja hitrost |
| 255 | 255 | SpeedChange=Sprememba hitrosti |
| 256 | 256 | MaximumTurningRate=Maksimalna hitrost obračanja (°/s) |
| 257 | 257 | AverageTurningRate=Povprečna hitrost obračanja (°/s) |
| 258 | +LengthOfTheLeg=Dolžina stranice |
|
| 259 | +LegSailingDomainRetrieverChain=Stranica |
java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties
| ... | ... | @@ -24,7 +24,7 @@ Team=团队 |
| 24 | 24 | SearchTag=搜索标记 |
| 25 | 25 | LeaderboardSailingDomainRetrieverChain=积分榜 |
| 26 | 26 | RaceSailingDomainRetrieverChain=比赛轮次 |
| 27 | -LegSailingDomainRetrieverChain=航段 |
|
| 27 | +LegOfCompetitorSailingDomainRetrieverChain=参赛队的航段 |
|
| 28 | 28 | GPSFixSailingDomainRetrieverChain=GPS 修复 |
| 29 | 29 | BravoFixSailingDomainRetrieverChain=Bravo 修复 |
| 30 | 30 | BravoFixTrackSailingDomainRetrieverChain=Bravo 跟踪 |
| ... | ... | @@ -249,9 +249,11 @@ IRM=IRM 代码(OCS、DNC、DNF 等) |
| 249 | 249 | XTE=交叉轨道误差 |
| 250 | 250 | AbsoluteXTE=绝对交叉轨道误差 |
| 251 | 251 | Day=天 |
| 252 | -CompetitorDay=参赛队日 |
|
| 253 | -CompetitorDaySailingDomainRetrieverChain=比赛轮次的参赛队日 |
|
| 252 | +CompetitorDay=参赛队天 |
|
| 253 | +CompetitorDaySailingDomainRetrieverChain=比赛轮次的参赛队天 |
|
| 254 | 254 | LowestSpeed=最低速度 |
| 255 | 255 | SpeedChange=速度变化 |
| 256 | 256 | MaximumTurningRate=最大回转率 (°/s) |
| 257 | 257 | AverageTurningRate=平均回转率 (°/s) |
| 258 | +LengthOfTheLeg=航段长度 |
|
| 259 | +LegSailingDomainRetrieverChain=航段 |
java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java
| ... | ... | @@ -98,9 +98,14 @@ public class SailingDataRetrievalChainDefinitions { |
| 98 | 98 | HasFoilingSegmentContext.class, FoilingSegmentsDataMiningSettings.class, FoilingSegmentsDataMiningSettings.createDefaultSettings(), "FoilingSegments"); |
| 99 | 99 | dataRetrieverChainDefinitions.add(foilingSegmentsRetrieverChainDefinition); |
| 100 | 100 | // |
| 101 | + final DataRetrieverChainDefinition<RacingEventService, HasTrackedLegContext> legRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( |
|
| 102 | + trackedRaceRetrieverChainDefinition, HasTrackedLegContext.class, "LegSailingDomainRetrieverChain"); |
|
| 103 | + legRetrieverChainDefinition.endWith(TrackedRaceRetrievalProcessor.class, TrackedLegRetrievalProcessor.class, |
|
| 104 | + HasTrackedLegContext.class, "Leg"); |
|
| 105 | + dataRetrieverChainDefinitions.add(legRetrieverChainDefinition); |
|
| 106 | + // |
|
| 101 | 107 | final DataRetrieverChainDefinition<RacingEventService, HasTrackedLegOfCompetitorContext> legOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( |
| 102 | - trackedRaceRetrieverChainDefinition, HasTrackedLegOfCompetitorContext.class, "LegSailingDomainRetrieverChain"); |
|
| 103 | - legOfCompetitorRetrieverChainDefinition.addAfter(TrackedRaceRetrievalProcessor.class, TrackedLegRetrievalProcessor.class, HasTrackedLegContext.class, "Leg"); |
|
| 108 | + legRetrieverChainDefinition, HasTrackedLegOfCompetitorContext.class, "LegOfCompetitorSailingDomainRetrieverChain"); |
|
| 104 | 109 | legOfCompetitorRetrieverChainDefinition.endWith(TrackedLegRetrievalProcessor.class, TrackedLegOfCompetitorRetrievalProcessor.class, |
| 105 | 110 | HasTrackedLegOfCompetitorContext.class, "LegOfCompetitor"); |
| 106 | 111 | dataRetrieverChainDefinitions.add(legOfCompetitorRetrieverChainDefinition); |
java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegContext.java
| ... | ... | @@ -2,8 +2,10 @@ package com.sap.sailing.datamining.data; |
| 2 | 2 | |
| 3 | 3 | import com.sap.sailing.domain.common.LegType; |
| 4 | 4 | import com.sap.sailing.domain.tracking.TrackedLeg; |
| 5 | +import com.sap.sse.common.Distance; |
|
| 5 | 6 | import com.sap.sse.datamining.annotations.Connector; |
| 6 | 7 | import com.sap.sse.datamining.annotations.Dimension; |
| 8 | +import com.sap.sse.datamining.annotations.Statistic; |
|
| 7 | 9 | |
| 8 | 10 | public interface HasTrackedLegContext { |
| 9 | 11 | |
| ... | ... | @@ -16,5 +18,6 @@ public interface HasTrackedLegContext { |
| 16 | 18 | public LegType getLegType(); |
| 17 | 19 | @Dimension(messageKey="LegNumber", ordinal=7) |
| 18 | 20 | public int getLegNumber(); |
| 19 | - |
|
| 21 | + @Statistic(messageKey = "LengthOfTheLeg", resultDecimals = 2) |
|
| 22 | + Distance getLegLength(); |
|
| 20 | 23 | } |
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegWithContext.java
| ... | ... | @@ -6,6 +6,7 @@ import com.sap.sailing.domain.common.LegType; |
| 6 | 6 | import com.sap.sailing.domain.common.NoWindException; |
| 7 | 7 | import com.sap.sailing.domain.tracking.TrackedLeg; |
| 8 | 8 | import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; |
| 9 | +import com.sap.sse.common.Distance; |
|
| 9 | 10 | import com.sap.sse.common.TimePoint; |
| 10 | 11 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 11 | 12 | |
| ... | ... | @@ -97,5 +98,9 @@ public class TrackedLegWithContext implements HasTrackedLegContext { |
| 97 | 98 | } |
| 98 | 99 | return at; |
| 99 | 100 | } |
| 100 | - |
|
| 101 | + |
|
| 102 | + @Override |
|
| 103 | + public Distance getLegLength() { |
|
| 104 | + return getTrackedLeg().getWindwardDistance(); |
|
| 105 | + } |
|
| 101 | 106 | } |
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DataImportProgressImpl.java
| ... | ... | @@ -55,7 +55,7 @@ public class DataImportProgressImpl implements DataImportProgress { |
| 55 | 55 | @Override |
| 56 | 56 | public void setCurrentSubProgress(DataImportSubProgress subProgress) { |
| 57 | 57 | if (subProgress != currentSubProgress) { |
| 58 | - logger.info("Master data import with operation ID "+getOperationId()+" moving from stage "+currentSubProgressPct+" to "+subProgress); |
|
| 58 | + logger.info("Master data import with operation ID "+getOperationId()+" moving from stage "+currentSubProgress+" to "+subProgress); |
|
| 59 | 59 | } |
| 60 | 60 | currentSubProgress = subProgress; |
| 61 | 61 | } |
java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java
| ... | ... | @@ -41,8 +41,6 @@ public class Activator implements BundleActivator { |
| 41 | 41 | private static final Logger logger = Logger.getLogger(Activator.class.getName()); |
| 42 | 42 | private static Activator INSTANCE; |
| 43 | 43 | |
| 44 | - private static final String DEFAULT_CLIENT_ID = "d29eae61621af3057db0e638232a027e96b1d2291b1b89a1481dfcac075b0bf4"; |
|
| 45 | - private static final String DEFAULT_CLIENT_SECRET = "537dbd14a84fcb470c91d85e8c4f8f7a356ac5ffc8727594d1bfe900ee5942ef"; |
|
| 46 | 44 | private static final String DEFAULT_REDIRECT_PROTOCOL = "https"; |
| 47 | 45 | private static final String DEFAULT_REDIRECT_HOST = "www.sapsailing.com"; |
| 48 | 46 | private static final String DEFAULT_REDIRECT_PORT = null; // port==null means default |
| ... | ... | @@ -60,8 +58,8 @@ public class Activator implements BundleActivator { |
| 60 | 58 | |
| 61 | 59 | public Activator() throws ClientProtocolException, IllegalStateException, IOException, ParseException { |
| 62 | 60 | logger.info(getClass().getName()+" constructor"); |
| 63 | - final String clientId = System.getProperty(CLIENT_ID_PROPERTY_NAME, DEFAULT_CLIENT_ID); |
|
| 64 | - final String clientSecret = System.getProperty(CLIENT_SECRET_PROPERTY_NAME, DEFAULT_CLIENT_SECRET); |
|
| 61 | + final String clientId = System.getProperty(CLIENT_ID_PROPERTY_NAME); |
|
| 62 | + final String clientSecret = System.getProperty(CLIENT_SECRET_PROPERTY_NAME); |
|
| 65 | 63 | final String clientRedirectProtocol = System.getProperty(CLIENT_REDIRECT_PROTOCOL_PROPERTY_NAME, DEFAULT_REDIRECT_PROTOCOL); |
| 66 | 64 | final String clientRedirectHost = System.getProperty(CLIENT_REDIRECT_HOST_PROPERTY_NAME, DEFAULT_REDIRECT_HOST); |
| 67 | 65 | final String clientRedirectPort = System.getProperty(CLIENT_REDIRECT_PORT_PROPERTY_NAME, DEFAULT_REDIRECT_PORT); |
java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF
| ... | ... | @@ -17,7 +17,8 @@ Require-Bundle: com.sap.sailing.domain, |
| 17 | 17 | com.sap.sailing.xrr.schema, |
| 18 | 18 | com.sap.sailing.domain.shared.android, |
| 19 | 19 | com.sun.xml.bind.jaxb-impl;bundle-version="2.3.0", |
| 20 | - com.sap.sse.security.common |
|
| 20 | + com.sap.sse.security.common, |
|
| 21 | + com.sap.sailing.manage2sail |
|
| 21 | 22 | Export-Package: com.sap.sailing.domain.swisstimingadapter, |
| 22 | 23 | com.sap.sailing.domain.swisstimingadapter.impl;x-friends:="com.sap.sailing.server.test,com.sap.sailing.mongodb.test,com.sap.sailing.domain.swisstimingadapter.persistence" |
| 23 | 24 | Import-Package: com.sap.sailing.xrr.schema, |
java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java
| ... | ... | @@ -3,7 +3,6 @@ package com.sap.sailing.domain.swisstimingadapter; |
| 3 | 3 | import java.io.IOException; |
| 4 | 4 | import java.net.UnknownHostException; |
| 5 | 5 | import java.text.ParseException; |
| 6 | -import java.util.List; |
|
| 7 | 6 | |
| 8 | 7 | import javax.xml.bind.JAXBException; |
| 9 | 8 | |
| ... | ... | @@ -19,8 +18,6 @@ public interface SwissTimingAdapter { |
| 19 | 18 | String NAME = "SwissTiming"; |
| 20 | 19 | String DEFAULT_URL = null; |
| 21 | 20 | |
| 22 | - List<com.sap.sailing.domain.swisstimingadapter.RaceRecord> getSwissTimingRaceRecords(String hostname, int port) throws InterruptedException, UnknownHostException, IOException, ParseException; |
|
| 23 | - |
|
| 24 | 21 | /** |
| 25 | 22 | * @param regattaToAddTo |
| 26 | 23 | * if <code>null</code>, an existing regatta by the name of the TracTrac event with the boat class name |
java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/AbstractSailMasterConnector.java
| ... | ... | @@ -4,6 +4,7 @@ import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | 5 | import java.io.OutputStream; |
| 6 | 6 | import java.net.SocketException; |
| 7 | +import java.net.URISyntaxException; |
|
| 7 | 8 | import java.net.UnknownHostException; |
| 8 | 9 | import java.text.DateFormat; |
| 9 | 10 | import java.text.DecimalFormat; |
| ... | ... | @@ -568,7 +569,7 @@ public abstract class AbstractSailMasterConnector extends SailMasterTransceiverI |
| 568 | 569 | + " which seems to be OK. I think we're connected in "+this+"!"); |
| 569 | 570 | } |
| 570 | 571 | } |
| 571 | - } catch (IOException e) { |
|
| 572 | + } catch (IOException | URISyntaxException e) { |
|
| 572 | 573 | logger.log(Level.INFO, "Exception trying to establish connection in "+this+". Trying again in 1s.", e); |
| 573 | 574 | disconnectAndWaitABit(); |
| 574 | 575 | } |
| ... | ... | @@ -576,7 +577,7 @@ public abstract class AbstractSailMasterConnector extends SailMasterTransceiverI |
| 576 | 577 | } |
| 577 | 578 | } |
| 578 | 579 | |
| 579 | - protected abstract void connect() throws IOException; |
|
| 580 | + protected abstract void connect() throws IOException, URISyntaxException; |
|
| 580 | 581 | |
| 581 | 582 | protected abstract boolean isConnected() throws IOException; |
| 582 | 583 |
java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorForUrlDownload.java
| ... | ... | @@ -4,6 +4,7 @@ import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | 5 | import java.io.OutputStream; |
| 6 | 6 | import java.net.HttpURLConnection; |
| 7 | +import java.net.URISyntaxException; |
|
| 7 | 8 | import java.net.URL; |
| 8 | 9 | import java.net.URLConnection; |
| 9 | 10 | import java.text.ParseException; |
| ... | ... | @@ -12,6 +13,7 @@ import java.util.logging.Logger; |
| 12 | 13 | import com.sap.sailing.domain.base.BoatClass; |
| 13 | 14 | import com.sap.sailing.domain.common.TrackedRaceStatusEnum; |
| 14 | 15 | import com.sap.sailing.domain.swisstimingadapter.MessageType; |
| 16 | +import com.sap.sailing.manage2sail.Activator; |
|
| 15 | 17 | import com.sap.sse.util.HttpUrlConnectionHelper; |
| 16 | 18 | |
| 17 | 19 | /** |
| ... | ... | @@ -67,9 +69,9 @@ public class SailMasterConnectorForUrlDownload extends AbstractSailMasterConnect |
| 67 | 69 | } |
| 68 | 70 | |
| 69 | 71 | @Override |
| 70 | - protected void connect() throws IOException { |
|
| 72 | + protected void connect() throws IOException, URISyntaxException { |
|
| 71 | 73 | logger.info("Connecting to "+raceDataUrl); |
| 72 | - urlConnection = HttpUrlConnectionHelper.redirectConnection(raceDataUrl); |
|
| 74 | + urlConnection = HttpUrlConnectionHelper.redirectConnection(Activator.getInstance().addAccessTokenToManage2SailUrl(raceDataUrl)); |
|
| 73 | 75 | } |
| 74 | 76 | |
| 75 | 77 | @Override |
java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java
| ... | ... | @@ -4,8 +4,6 @@ import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | 5 | import java.net.URL; |
| 6 | 6 | import java.net.URLConnection; |
| 7 | -import java.net.UnknownHostException; |
|
| 8 | -import java.text.ParseException; |
|
| 9 | 7 | import java.util.ArrayList; |
| 10 | 8 | import java.util.HashMap; |
| 11 | 9 | import java.util.List; |
| ... | ... | @@ -59,14 +57,6 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter { |
| 59 | 57 | } |
| 60 | 58 | |
| 61 | 59 | @Override |
| 62 | - public List<com.sap.sailing.domain.swisstimingadapter.RaceRecord> getSwissTimingRaceRecords(String hostname, |
|
| 63 | - int port) throws InterruptedException, UnknownHostException, IOException, ParseException { |
|
| 64 | - List<com.sap.sailing.domain.swisstimingadapter.RaceRecord> result = new ArrayList<com.sap.sailing.domain.swisstimingadapter.RaceRecord>(); |
|
| 65 | - // TODO --> Frank: this needs to come from Manage2Sail and its JSON document somehow... |
|
| 66 | - return result; |
|
| 67 | - } |
|
| 68 | - |
|
| 69 | - @Override |
|
| 70 | 60 | public RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, |
| 71 | 61 | String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, |
| 72 | 62 | StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, |
java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF
| ... | ... | @@ -35,7 +35,9 @@ Require-Bundle: com.sap.sailing.domain, |
| 35 | 35 | org.mongodb.driver-sync;bundle-version="4.3.1", |
| 36 | 36 | org.apache.commons.math3;bundle-version="3.5.0", |
| 37 | 37 | org.apache.shiro.core;bundle-version="1.2.2", |
| 38 | - org.hamcrest;bundle-version="2.2.0" |
|
| 38 | + org.hamcrest;bundle-version="2.2.0", |
|
| 39 | + net.bytebuddy.byte-buddy;bundle-version="1.12.18", |
|
| 40 | + net.bytebuddy.byte-buddy-agent;bundle-version="1.12.18" |
|
| 39 | 41 | Export-Package: com.sap.sailing.domain.test, |
| 40 | 42 | com.sap.sailing.domain.test.mock |
| 41 | 43 | Import-Package: org.apache.commons.math;version="2.1.0", |
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java
| ... | ... | @@ -82,34 +82,50 @@ public class MarkPositionReceiver extends AbstractReceiverWithQueue<IControl, IP |
| 82 | 82 | |
| 83 | 83 | @Override |
| 84 | 84 | protected void handleEvent(Util.Triple<IControl, IPosition, Integer> event) { |
| 85 | - final String markCourseArea = event.getA() == null ? null : event.getA().getCourseArea(); |
|
| 86 | - final String raceCourseArea = tractracRace.getCourseArea(); |
|
| 87 | - // if mark and race both specify a course area, accept the mark position only |
|
| 88 | - // if the course area matches: |
|
| 89 | - if (markCourseArea == null || raceCourseArea == null || markCourseArea.equals(raceCourseArea)) { |
|
| 90 | - if (received++ % 1000 == 0) { |
|
| 91 | - System.out.print("M"); |
|
| 92 | - if ((received / 1000 + 1) % 80 == 0) { |
|
| 93 | - System.out.println(); |
|
| 94 | - } |
|
| 85 | + /* TODO bug 5919 (https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=5919) |
|
| 86 | + * |
|
| 87 | + * We would like to exclude mark positions coming from marks that do not belong |
|
| 88 | + * to the race for which this receiver is subscribed. The IControl interface offers |
|
| 89 | + * a getCourseArea() method, and so does IRace. It would seem natural to do the following: |
|
| 90 | + * |
|
| 91 | + final String markCourseArea = event.getA() == null ? null : event.getA().getCourseArea(); |
|
| 92 | + final String raceCourseArea = tractracRace.getCourseArea(); |
|
| 93 | + // if mark and race both specify a course area, accept the mark position only |
|
| 94 | + // if the course area matches: |
|
| 95 | + if (markCourseArea == null || raceCourseArea == null || markCourseArea.equals(raceCourseArea)) { |
|
| 96 | + * |
|
| 97 | + * However, TracAPI updates Control objects' fields from the race data currently being loaded. |
|
| 98 | + * Hence, if multiple races are loaded concurrently, as is typically the case upon any server |
|
| 99 | + * re-start, particularly of course a larger archive of many events, the IControl.getCourseArea() |
|
| 100 | + * method will return unpredictable values as the IControl object for marks with the same ID |
|
| 101 | + * will be shared across all of TracAPI, and hence values from any random race being loaded |
|
| 102 | + * may end up in the courseArea field. |
|
| 103 | + * |
|
| 104 | + * Therefore, for the time being, we cannot use this criterion to filter mark fixes. Of course, |
|
| 105 | + * it would also help if TracTrac / TracAPI filtered those already on the server side. |
|
| 106 | + */ |
|
| 107 | + if (received++ % 1000 == 0) { |
|
| 108 | + System.out.print("M"); |
|
| 109 | + if ((received / 1000 + 1) % 80 == 0) { |
|
| 110 | + System.out.println(); |
|
| 95 | 111 | } |
| 96 | - if (!singleMarksEnsuredAlready) { |
|
| 97 | - singleMarksEnsuredAlready = true; |
|
| 98 | - ensureAllSingleMarksOfCourseAreaAreCreated(tractracRace); |
|
| 99 | - } |
|
| 100 | - Mark mark = getDomainFactory().getMark(new ControlPointAdapter(event.getA()), event.getC()); |
|
| 101 | - DynamicTrackedRace trackedRace = getTrackedRace(tractracRace); |
|
| 102 | - if (trackedRace != null) { |
|
| 103 | - GPSFixMoving markPosition = getDomainFactory().createGPSFixMoving(event.getB()); |
|
| 104 | - if (getSimulator() != null) { |
|
| 105 | - getSimulator().scheduleMarkPosition(mark, markPosition); |
|
| 106 | - } else { |
|
| 107 | - trackedRace.recordFix(mark, markPosition); |
|
| 108 | - } |
|
| 112 | + } |
|
| 113 | + if (!singleMarksEnsuredAlready) { |
|
| 114 | + singleMarksEnsuredAlready = true; |
|
| 115 | + ensureAllSingleMarksOfCourseAreaAreCreated(tractracRace); |
|
| 116 | + } |
|
| 117 | + Mark mark = getDomainFactory().getMark(new ControlPointAdapter(event.getA()), event.getC()); |
|
| 118 | + DynamicTrackedRace trackedRace = getTrackedRace(tractracRace); |
|
| 119 | + if (trackedRace != null) { |
|
| 120 | + GPSFixMoving markPosition = getDomainFactory().createGPSFixMoving(event.getB()); |
|
| 121 | + if (getSimulator() != null) { |
|
| 122 | + getSimulator().scheduleMarkPosition(mark, markPosition); |
|
| 109 | 123 | } else { |
| 110 | - logger.warning("Couldn't find tracked race for race " + tractracRace.getName() |
|
| 111 | - + ". Dropping mark position event " + event); |
|
| 124 | + trackedRace.recordFix(mark, markPosition); |
|
| 112 | 125 | } |
| 126 | + } else { |
|
| 127 | + logger.warning("Couldn't find tracked race for race " + tractracRace.getName() |
|
| 128 | + + ". Dropping mark position event " + event); |
|
| 113 | 129 | } |
| 114 | 130 | } |
| 115 | 131 |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
| ... | ... | @@ -416,6 +416,9 @@ public interface TrackedRace |
| 416 | 416 | * Obtains estimated interpolated wind information for a given position and time point. The information is taken |
| 417 | 417 | * from all wind sources available except for those listed in <code>windSourcesToExclude</code>, using the |
| 418 | 418 | * confidences of the wind values provided by the various sources during averaging. |
| 419 | + * |
|
| 420 | + * @return the wind vector for the time and place given; or {@code null} if no wind speed/direction can be |
|
| 421 | + * determined, e.g., because there is no non-excluded wind source available |
|
| 419 | 422 | */ |
| 420 | 423 | Wind getWind(Position p, TimePoint at, Set<WindSource> windSourcesToExclude); |
| 421 | 424 | |
| ... | ... | @@ -750,6 +753,9 @@ public interface TrackedRace |
| 750 | 753 | * and time point <code>at</code>, using the particular wind source's averaging interval. The confidences delivered |
| 751 | 754 | * by each wind source are used during computing the averaged result across the wind sources. The result has the |
| 752 | 755 | * averaged confidence attached. |
| 756 | + * |
|
| 757 | + * @return the wind vector for the time and place given, with a confidence attached; or {@code null} if no wind |
|
| 758 | + * speed/direction can be determined, e.g., because there is no non-excluded wind source available |
|
| 753 | 759 | */ |
| 754 | 760 | WindWithConfidence<Util.Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at, |
| 755 | 761 | Set<WindSource> windSourcesToExclude); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/qrcode/QRCodePlace.java
| ... | ... | @@ -7,6 +7,7 @@ import java.util.logging.Logger; |
| 7 | 7 | |
| 8 | 8 | import com.google.gwt.place.shared.PlaceTokenizer; |
| 9 | 9 | import com.google.gwt.user.client.Window; |
| 10 | +import com.sap.sailing.domain.common.BranchIOConstants; |
|
| 10 | 11 | import com.sap.sailing.domain.common.MailInvitationType; |
| 11 | 12 | import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; |
| 12 | 13 | import com.sap.sse.common.Util.Pair; |
| ... | ... | @@ -29,10 +30,14 @@ import com.sap.sse.gwt.client.AbstractBasePlace; |
| 29 | 30 | */ |
| 30 | 31 | public class QRCodePlace extends AbstractBasePlace { |
| 31 | 32 | private static final Logger logger = Logger.getLogger(QRCodePlace.class.getName()); |
| 32 | - public static final String PARAM_REGATTA_NAME = "regatta_name"; |
|
| 33 | - public static final String PARAM_REGATTA_SECRET = "secret"; |
|
| 33 | + private static final String PARAM_REGATTA_NAME = "regatta_name"; |
|
| 34 | + private static final String PARAM_REGATTA_SECRET = "secret"; |
|
| 34 | 35 | private static final String PARAM_MODE = "mode"; |
| 35 | - public static final String PARAM_SERVER = "server"; |
|
| 36 | + private static final String PARAM_SERVER = "server"; |
|
| 37 | + private static final String PARAM_SERVER_URL = "server_url"; |
|
| 38 | + private static final String PARAM_DEVICE_CONFIG_ID = "device_config_identifier"; |
|
| 39 | + private static final String PARAM_DEVICE_CONFIG_UUID = "device_config_uuid"; |
|
| 40 | + private static final String PARAM_TOKEN = "token"; |
|
| 36 | 41 | |
| 37 | 42 | private UUID eventId; |
| 38 | 43 | private UUID competitorId; |
| ... | ... | @@ -44,6 +49,10 @@ public class QRCodePlace extends AbstractBasePlace { |
| 44 | 49 | private InvitationMode mode; |
| 45 | 50 | private String rawCheckInUrl; |
| 46 | 51 | private String targetServer; |
| 52 | + private String serverUrl; |
|
| 53 | + private String deviceConfigIdentifier; |
|
| 54 | + private String deviceConfigUuid; |
|
| 55 | + private String token; |
|
| 47 | 56 | |
| 48 | 57 | public enum InvitationMode { |
| 49 | 58 | COMPETITOR(MailInvitationType.SailInsight1), |
| ... | ... | @@ -74,6 +83,12 @@ public class QRCodePlace extends AbstractBasePlace { |
| 74 | 83 | if (publicRegattaName == null || regattaRegistrationLinkSecret == null || targetServer == null) { |
| 75 | 84 | logger.severe("Missing parameter for regatta, secret or server"); |
| 76 | 85 | } |
| 86 | + } else if (isRaceManagerAppRequest()) { |
|
| 87 | + serverUrl = Window.Location.getParameter(PARAM_SERVER_URL); |
|
| 88 | + targetServer = serverUrl; |
|
| 89 | + deviceConfigIdentifier = Window.Location.getParameter(PARAM_DEVICE_CONFIG_ID); |
|
| 90 | + deviceConfigUuid = Window.Location.getParameter(PARAM_DEVICE_CONFIG_UUID); |
|
| 91 | + token = Window.Location.getParameter(PARAM_TOKEN); |
|
| 77 | 92 | } else { |
| 78 | 93 | rawCheckInUrl = Window.Location.getParameter(DeviceMappingConstants.URL_CHECKIN_URL); |
| 79 | 94 | if (rawCheckInUrl != null) { |
| ... | ... | @@ -176,12 +191,28 @@ public class QRCodePlace extends AbstractBasePlace { |
| 176 | 191 | } |
| 177 | 192 | |
| 178 | 193 | public String getPublicInviteBranchIOUrl(MailInvitationType mailInvitationType) { |
| 179 | - return mailInvitationType.getBranchIOopenRegattaURL() + "?" + QRCodePlace.PARAM_REGATTA_NAME + "=" |
|
| 180 | - + encodeUrl(publicRegattaName) + "&" + QRCodePlace.PARAM_REGATTA_SECRET + "=" |
|
| 181 | - + encodeUrl(regattaRegistrationLinkSecret) + "&" + QRCodePlace.PARAM_SERVER + "=" |
|
| 194 | + return mailInvitationType.getBranchIOopenRegattaURL() + "?" + PARAM_REGATTA_NAME + "=" |
|
| 195 | + + encodeUrl(publicRegattaName) + "&" + PARAM_REGATTA_SECRET + "=" |
|
| 196 | + + encodeUrl(regattaRegistrationLinkSecret) + "&" + PARAM_SERVER + "=" |
|
| 182 | 197 | + encodeUrl(targetServer); |
| 183 | 198 | } |
| 184 | 199 | |
| 200 | + public String getRaceManagerAppUrl() { |
|
| 201 | + String url; |
|
| 202 | + if (serverUrl != null && deviceConfigIdentifier != null && deviceConfigUuid != null) { |
|
| 203 | + url = BranchIOConstants.RACEMANAGER_APP_BRANCH_QUICK_LINK |
|
| 204 | + + "?" + PARAM_SERVER_URL + "=" + encodeUrl(serverUrl) |
|
| 205 | + + "&" + PARAM_DEVICE_CONFIG_ID + "=" + encodeUrl(deviceConfigIdentifier) |
|
| 206 | + + "&" + PARAM_DEVICE_CONFIG_UUID + "=" + encodeUrl(deviceConfigUuid); |
|
| 207 | + if (token != null) { |
|
| 208 | + url += "&" + PARAM_TOKEN + "=" + encodeUrl(token); |
|
| 209 | + } |
|
| 210 | + } else { |
|
| 211 | + url = null; |
|
| 212 | + } |
|
| 213 | + return url; |
|
| 214 | + } |
|
| 215 | + |
|
| 185 | 216 | public InvitationMode getMode() { |
| 186 | 217 | return mode; |
| 187 | 218 | } |
| ... | ... | @@ -227,7 +258,9 @@ public class QRCodePlace extends AbstractBasePlace { |
| 227 | 258 | return "QRCodePlace [eventId=" + eventId + ", competitorId=" + competitorId + ", boatId=" + boatId + ", markId=" |
| 228 | 259 | + markId + ", leaderboardName=" + leaderboardName + ", publicRegattaName=" + publicRegattaName |
| 229 | 260 | + ", regattaRegistrationLinkSecret=" + regattaRegistrationLinkSecret + ", mode=" + mode |
| 230 | - + ", rawCheckInUrl=" + rawCheckInUrl + ", targetServer=" + targetServer + "]"; |
|
| 261 | + + ", rawCheckInUrl=" + rawCheckInUrl + ", targetServer=" + targetServer + ", serverUrl=" + serverUrl |
|
| 262 | + + ", deviceConfigIdentifier=" + deviceConfigIdentifier + ", deviceConfigUuid=" + deviceConfigUuid |
|
| 263 | + + ", token=" + token + "]"; |
|
| 231 | 264 | } |
| 232 | 265 | |
| 233 | 266 | public static class Tokenizer implements PlaceTokenizer<QRCodePlace> { |
| ... | ... | @@ -257,8 +290,19 @@ public class QRCodePlace extends AbstractBasePlace { |
| 257 | 290 | * @return true if it is a public invite request (former PUBLIC_INVITE or PUBLIC_INVITE3 mode) |
| 258 | 291 | */ |
| 259 | 292 | static boolean isPublicInviteRequest() { |
| 260 | - return Window.Location.getParameter(QRCodePlace.PARAM_REGATTA_NAME) != null |
|
| 261 | - && Window.Location.getParameter(QRCodePlace.PARAM_REGATTA_SECRET) != null |
|
| 262 | - && Window.Location.getParameter(QRCodePlace.PARAM_SERVER) != null; |
|
| 293 | + return Window.Location.getParameter(PARAM_REGATTA_NAME) != null |
|
| 294 | + && Window.Location.getParameter(PARAM_REGATTA_SECRET) != null |
|
| 295 | + && Window.Location.getParameter(PARAM_SERVER) != null; |
|
| 296 | + } |
|
| 297 | + |
|
| 298 | + /** |
|
| 299 | + * Check if available URL parameter are indicating a public invite request. |
|
| 300 | + * |
|
| 301 | + * @return true if it is a public invite request (former PUBLIC_INVITE or PUBLIC_INVITE3 mode) |
|
| 302 | + */ |
|
| 303 | + static boolean isRaceManagerAppRequest() { |
|
| 304 | + return Window.Location.getParameter(PARAM_SERVER_URL) != null |
|
| 305 | + && Window.Location.getParameter(PARAM_DEVICE_CONFIG_ID) != null |
|
| 306 | + && Window.Location.getParameter(PARAM_DEVICE_CONFIG_UUID) != null; |
|
| 263 | 307 | } |
| 264 | 308 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/qrcode/QRCodePresenter.java
| ... | ... | @@ -142,6 +142,10 @@ public class QRCodePresenter { |
| 142 | 142 | // as the event is most likely displayed on a different server anyway, do not load additional data |
| 143 | 143 | dataCollector = new DataCollector(view); |
| 144 | 144 | dataCollector.proceedIfFinished(); |
| 145 | + } else if (QRCodePlace.isRaceManagerAppRequest()) { |
|
| 146 | + logger.info("QR Code for race manager app to be shown"); |
|
| 147 | + dataCollector = new DataCollector(view); |
|
| 148 | + dataCollector.proceedIfFinished(); |
|
| 145 | 149 | } else { |
| 146 | 150 | logger.severe("QR Code cannot be created. Request type cannot be identified."); |
| 147 | 151 | } |
| ... | ... | @@ -293,6 +297,9 @@ public class QRCodePresenter { |
| 293 | 297 | logger.info("About to show QR Code for public regatta invite"); |
| 294 | 298 | view.showPublic(place.getPublicRegattaName(), |
| 295 | 299 | place.getPublicInviteBranchIOUrl(place.getMode().getMailInvitationType())); |
| 300 | + } else if (QRCodePlace.isRaceManagerAppRequest()) { |
|
| 301 | + logger.info("About to show QR Code for race manager app"); |
|
| 302 | + view.showRaceManagerApp(place.getRaceManagerAppUrl()); |
|
| 296 | 303 | } else { |
| 297 | 304 | logger.severe("Cannot identify request type."); |
| 298 | 305 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/qrcode/QRCodeView.java
| ... | ... | @@ -89,8 +89,24 @@ public class QRCodeView extends Composite { |
| 89 | 89 | |
| 90 | 90 | public void showPublic(String publicRegattaName, String publicInviteBranchIOUrl) { |
| 91 | 91 | logger.info("Showing QR Code for public regatta invite"); |
| 92 | - titleDivUi.setInnerText(StringMessages.INSTANCE.qrCodeTitleOpenRegatta(publicRegattaName)); |
|
| 93 | - showQrCodeForURL(publicInviteBranchIOUrl); |
|
| 92 | + if (publicInviteBranchIOUrl != null) { |
|
| 93 | + titleDivUi.setInnerText(StringMessages.INSTANCE.qrCodeTitleOpenRegatta(publicRegattaName)); |
|
| 94 | + showQrCodeForURL(publicInviteBranchIOUrl); |
|
| 95 | + } else { |
|
| 96 | + titleDivUi.setInnerText(StringMessages.INSTANCE.qrCodeTitleErrorCreatingQrCode()); |
|
| 97 | + } |
|
| 98 | + } |
|
| 99 | + |
|
| 100 | + public void showRaceManagerApp(String raceManagerAppBranchIOUrl) { |
|
| 101 | + logger.info("Showing QR Code for race manager app"); |
|
| 102 | + titleDivUi.setInnerText(StringMessages.INSTANCE.qrCodeTitleOpenRaceManagerApp()); |
|
| 103 | + if (raceManagerAppBranchIOUrl != null) { |
|
| 104 | + subtitleDivUi.setInnerText(StringMessages.INSTANCE.qrCodeSubTitleOpenRaceManagerApp()); |
|
| 105 | + showQrCodeForURL(raceManagerAppBranchIOUrl); |
|
| 106 | + } else { |
|
| 107 | + titleDivUi.setInnerText(StringMessages.INSTANCE.qrCodeTitleErrorCreatingQrCode()); |
|
| 108 | + } |
|
| 109 | + |
|
| 94 | 110 | } |
| 95 | 111 | |
| 96 | 112 | public void showBouyTender(QRCodeEvent event, String leaderboardName, String branchIoUrl) { |
| ... | ... | @@ -103,7 +119,11 @@ public class QRCodeView extends Composite { |
| 103 | 119 | } else { |
| 104 | 120 | logger.info("No event available"); |
| 105 | 121 | } |
| 106 | - showQrCodeForURL(branchIoUrl); |
|
| 122 | + if (branchIoUrl != null) { |
|
| 123 | + showQrCodeForURL(branchIoUrl); |
|
| 124 | + } else { |
|
| 125 | + titleDivUi.setInnerText(StringMessages.INSTANCE.qrCodeTitleErrorCreatingQrCode()); |
|
| 126 | + } |
|
| 107 | 127 | showEventImageIfPossible(event); |
| 108 | 128 | } |
| 109 | 129 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
| ... | ... | @@ -4,6 +4,12 @@ |
| 4 | 4 | <div id="mainContent"> |
| 5 | 5 | <h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4> |
| 6 | 6 | <div class="innerContent"> |
| 7 | + <h5 class="articleSubheadline">November 2023</h5> |
|
| 8 | + <ul class="bulletList"> |
|
| 9 | + <li>Added statistics for length of a leg to the Data Mining module.</li> |
|
| 10 | + <li>Added tool tips to the drop-down lists for selecting the competitor chart data type |
|
| 11 | + and the data type for displaying as the tail color on the map</li> |
|
| 12 | + </ul> |
|
| 7 | 13 | <h5 class="articleSubheadline">October 2023</h5> |
| 8 | 14 | <ul class="bulletList"> |
| 9 | 15 | <li>Competitor charts sometimes wouldn't update properly in live mode. This has now been fixed.</li> |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java
| ... | ... | @@ -13,10 +13,11 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; |
| 13 | 13 | import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; |
| 14 | 14 | import com.sap.sse.common.TimePoint; |
| 15 | 15 | import com.sap.sse.common.TimeRange; |
| 16 | +import com.sap.sse.common.Util.Pair; |
|
| 16 | 17 | import com.sap.sse.common.impl.TimeRangeImpl; |
| 17 | 18 | import com.sap.sse.gwt.client.async.TimeRangeAsyncAction; |
| 18 | 19 | |
| 19 | -public class GetBoatPositionsAction implements TimeRangeAsyncAction<CompactBoatPositionsDTO, String> { |
|
| 20 | +public class GetBoatPositionsAction implements TimeRangeAsyncAction<CompactBoatPositionsDTO, Pair<String, DetailType>> { |
|
| 20 | 21 | private final SailingServiceAsync sailingService; |
| 21 | 22 | private final RegattaAndRaceIdentifier raceIdentifier; |
| 22 | 23 | private final Map<CompetitorDTO, Date> from; |
| ... | ... | @@ -42,27 +43,27 @@ public class GetBoatPositionsAction implements TimeRangeAsyncAction<CompactBoatP |
| 42 | 43 | } |
| 43 | 44 | |
| 44 | 45 | @Override |
| 45 | - public void execute(Map<String, TimeRange> timeRanges, AsyncCallback<CompactBoatPositionsDTO> callback) { |
|
| 46 | + public void execute(Map<Pair<String, DetailType>, TimeRange> timeRanges, AsyncCallback<CompactBoatPositionsDTO> callback) { |
|
| 46 | 47 | final Map<String, Date> fromByCompetitorIdAsString = new HashMap<String, Date>(); |
| 47 | 48 | final Map<String, Date> toByCompetitorIdAsString = new HashMap<String, Date>(); |
| 48 | - for (final Map.Entry<String, TimeRange> entry : timeRanges.entrySet()) { |
|
| 49 | + for (final Map.Entry<Pair<String, DetailType>, TimeRange> entry : timeRanges.entrySet()) { |
|
| 49 | 50 | final Date from = entry.getValue().from().asDate(); |
| 50 | 51 | final Date to = entry.getValue().to().asDate(); |
| 51 | - fromByCompetitorIdAsString.put(entry.getKey(), from); |
|
| 52 | - toByCompetitorIdAsString.put(entry.getKey(), to); |
|
| 52 | + fromByCompetitorIdAsString.put(entry.getKey().getA(), from); |
|
| 53 | + toByCompetitorIdAsString.put(entry.getKey().getA(), to); |
|
| 53 | 54 | } |
| 54 | 55 | sailingService.getBoatPositions(raceIdentifier, fromByCompetitorIdAsString, toByCompetitorIdAsString, |
| 55 | 56 | extrapolate, detailType, leaderboardName, leaderboardGroupName, leaderboardGroupId, callback); |
| 56 | 57 | } |
| 57 | 58 | |
| 58 | 59 | @Override |
| 59 | - public Map<String, TimeRange> getTimeRanges() { |
|
| 60 | - final Map<String, TimeRange> timeRangeByCompetitorId = new HashMap<>(from.size()); |
|
| 60 | + public Map<Pair<String, DetailType>, TimeRange> getTimeRanges() { |
|
| 61 | + final Map<Pair<String, DetailType>, TimeRange> timeRangeByCompetitorId = new HashMap<>(from.size()); |
|
| 61 | 62 | for (final Map.Entry<CompetitorDTO, Date> entry : from.entrySet()) { |
| 62 | 63 | final Date fromDate = entry.getValue(); |
| 63 | 64 | final Date toDate = to.get(entry.getKey()); |
| 64 | 65 | if (fromDate != null && toDate != null) { |
| 65 | - timeRangeByCompetitorId.put(entry.getKey().getIdAsString(), |
|
| 66 | + timeRangeByCompetitorId.put(new Pair<>(entry.getKey().getIdAsString(), detailType), |
|
| 66 | 67 | new TimeRangeImpl(TimePoint.of(fromDate), TimePoint.of(toDate), /* toIsInclusive */ true)); |
| 67 | 68 | } |
| 68 | 69 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsCallback.java
| ... | ... | @@ -2,10 +2,13 @@ package com.sap.sailing.gwt.ui.actions; |
| 2 | 2 | |
| 3 | 3 | import java.util.ArrayList; |
| 4 | 4 | import java.util.Comparator; |
| 5 | +import java.util.HashMap; |
|
| 5 | 6 | import java.util.List; |
| 6 | 7 | import java.util.Map; |
| 8 | +import java.util.Map.Entry; |
|
| 7 | 9 | |
| 8 | 10 | import com.google.gwt.user.client.rpc.AsyncCallback; |
| 11 | +import com.sap.sailing.domain.common.DetailType; |
|
| 9 | 12 | import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; |
| 10 | 13 | import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType; |
| 11 | 14 | import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegTypeIterable; |
| ... | ... | @@ -17,25 +20,35 @@ import com.sap.sse.common.impl.MultiTimeRangeImpl; |
| 17 | 20 | import com.sap.sse.gwt.client.async.TimeRangeAsyncCallback; |
| 18 | 21 | |
| 19 | 22 | /** |
| 20 | - * The third type parameter ({@link String}) for {@link TimeRangeAsyncCallback} represents the stringified |
|
| 21 | - * competitor IDs which form the key for the result structure. |
|
| 23 | + * The third type parameter ({@link Pair<String, DetailType>}) for {@link TimeRangeAsyncCallback} represents the stringified |
|
| 24 | + * competitor IDs and the DetailType which form the key for the result structure. |
|
| 22 | 25 | */ |
| 23 | 26 | public class GetBoatPositionsCallback |
| 24 | - implements TimeRangeAsyncCallback<CompactBoatPositionsDTO, GPSFixDTOWithSpeedWindTackAndLegTypeIterable, String> { |
|
| 27 | + implements TimeRangeAsyncCallback<CompactBoatPositionsDTO, GPSFixDTOWithSpeedWindTackAndLegTypeIterable, Pair<String, DetailType>> { |
|
| 28 | + private final DetailType detailType; |
|
| 25 | 29 | private final AsyncCallback<CompactBoatPositionsDTO> callback; |
| 26 | 30 | |
| 27 | - public GetBoatPositionsCallback(AsyncCallback<CompactBoatPositionsDTO> callback) { |
|
| 31 | + public GetBoatPositionsCallback(DetailType detailType, AsyncCallback<CompactBoatPositionsDTO> callback) { |
|
| 32 | + this.detailType = detailType; |
|
| 28 | 33 | this.callback = callback; |
| 29 | 34 | } |
| 30 | 35 | |
| 31 | 36 | @Override |
| 32 | - public Map<String, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> unzipResult(CompactBoatPositionsDTO result) { |
|
| 33 | - return result.getBoatPositions(); |
|
| 37 | + public Map<Pair<String, DetailType>, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> unzipResult(CompactBoatPositionsDTO result) { |
|
| 38 | + final Map<Pair<String, DetailType>, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> unzippedResult = new HashMap<>(); |
|
| 39 | + for (final Entry<String, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> i : result.getBoatPositions().entrySet()) { |
|
| 40 | + unzippedResult.put(new Pair<>(i.getKey(), detailType), i.getValue()); |
|
| 41 | + } |
|
| 42 | + return unzippedResult; |
|
| 34 | 43 | } |
| 35 | 44 | |
| 36 | 45 | @Override |
| 37 | - public CompactBoatPositionsDTO zipSubResults(Map<String, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> subResultMap) { |
|
| 38 | - return CompactBoatPositionsDTO.fromCompetitorIds(subResultMap); |
|
| 46 | + public CompactBoatPositionsDTO zipSubResults(Map<Pair<String, DetailType>, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> subResultMap) { |
|
| 47 | + final Map<String, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> convertedSubResultMap = new HashMap<>(); |
|
| 48 | + for (final Entry<Pair<String, DetailType>, GPSFixDTOWithSpeedWindTackAndLegTypeIterable> i : subResultMap.entrySet()) { |
|
| 49 | + convertedSubResultMap.put(i.getKey().getA(), i.getValue()); |
|
| 50 | + } |
|
| 51 | + return CompactBoatPositionsDTO.fromCompetitorIds(convertedSubResultMap); |
|
| 39 | 52 | } |
| 40 | 53 | |
| 41 | 54 | @Override |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetCompetitorsRaceDataAction.java
| ... | ... | @@ -17,9 +17,10 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; |
| 17 | 17 | import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; |
| 18 | 18 | import com.sap.sse.common.TimePoint; |
| 19 | 19 | import com.sap.sse.common.TimeRange; |
| 20 | +import com.sap.sse.common.Util.Pair; |
|
| 20 | 21 | import com.sap.sse.gwt.client.async.TimeRangeAsyncAction; |
| 21 | 22 | |
| 22 | -public class GetCompetitorsRaceDataAction implements TimeRangeAsyncAction<CompetitorsRaceDataDTO, CompetitorDTO> { |
|
| 23 | +public class GetCompetitorsRaceDataAction implements TimeRangeAsyncAction<CompetitorsRaceDataDTO, Pair<CompetitorDTO, DetailType>> { |
|
| 23 | 24 | private final SailingServiceAsync sailingService; |
| 24 | 25 | private final RegattaAndRaceIdentifier raceIdentifier; |
| 25 | 26 | private final List<CompetitorDTO> competitors; |
| ... | ... | @@ -47,28 +48,36 @@ public class GetCompetitorsRaceDataAction implements TimeRangeAsyncAction<Compet |
| 47 | 48 | } |
| 48 | 49 | |
| 49 | 50 | @Override |
| 50 | - public void execute(Map<CompetitorDTO, TimeRange> timeRanges, AsyncCallback<CompetitorsRaceDataDTO> callback) { |
|
| 51 | + public void execute(Map<Pair<CompetitorDTO, DetailType>, TimeRange> timeRanges, AsyncCallback<CompetitorsRaceDataDTO> callback) { |
|
| 51 | 52 | if (timeRanges != null && !timeRanges.isEmpty()) { |
| 53 | + DetailType detailType = null; |
|
| 54 | + final List<CompetitorDTO> competitors = new ArrayList<>(); |
|
| 52 | 55 | TimeRange timeRange = null; |
| 53 | - for (final Entry<CompetitorDTO, TimeRange> e : timeRanges.entrySet()) { |
|
| 56 | + for (final Entry<Pair<CompetitorDTO, DetailType>, TimeRange> e : timeRanges.entrySet()) { |
|
| 57 | + competitors.add(e.getKey().getA()); |
|
| 58 | + if (detailType == null) { |
|
| 59 | + detailType = e.getKey().getB(); |
|
| 60 | + } else if (detailType != e.getKey().getB()) { |
|
| 61 | + throw new IllegalArgumentException("The detail types must be the same; got "+detailType+" and "+e.getKey().getB()); |
|
| 62 | + } |
|
| 54 | 63 | timeRange = timeRange == null ? e.getValue() : timeRange.extend(e.getValue()); |
| 55 | 64 | } |
| 56 | 65 | GWT.log("Calling getCompetitorsRaceData("+raceIdentifier+", "+new ArrayList<>(timeRanges.keySet())+ |
| 57 | 66 | ", "+timeRange.from()+", "+timeRange.to()+", "+stepSizeInMs+", "+detailType+", "+leaderboardGroupName+", "+ |
| 58 | 67 | leaderboardGroupId+", "+leaderboardName); |
| 59 | - sailingService.getCompetitorsRaceData(raceIdentifier, new ArrayList<>(timeRanges.keySet()), timeRange.from().asDate(), |
|
| 68 | + sailingService.getCompetitorsRaceData(raceIdentifier, competitors, timeRange.from().asDate(), |
|
| 60 | 69 | timeRange.to().asDate(), stepSizeInMs, detailType, leaderboardGroupName, leaderboardGroupId, |
| 61 | 70 | leaderboardName, callback); |
| 62 | 71 | } |
| 63 | 72 | } |
| 64 | 73 | |
| 65 | 74 | @Override |
| 66 | - public Map<CompetitorDTO, TimeRange> getTimeRanges() { |
|
| 75 | + public Map<Pair<CompetitorDTO, DetailType>, TimeRange> getTimeRanges() { |
|
| 67 | 76 | // we'll use the same time range for all competitors |
| 68 | 77 | final TimeRange timeRange = TimeRange.create(TimePoint.of(fromDate), TimePoint.of(toDate)); |
| 69 | - final Map<CompetitorDTO, TimeRange> result = new HashMap<>(); |
|
| 78 | + final Map<Pair<CompetitorDTO, DetailType>, TimeRange> result = new HashMap<>(); |
|
| 70 | 79 | for (final CompetitorDTO competitor : competitors) { |
| 71 | - result.put(competitor, timeRange); |
|
| 80 | + result.put(new Pair<>(competitor, detailType), timeRange); |
|
| 72 | 81 | } |
| 73 | 82 | return result; |
| 74 | 83 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractCompetitorRegistrationDialog.java
| ... | ... | @@ -32,6 +32,7 @@ public abstract class AbstractCompetitorRegistrationDialog extends DataEntryDial |
| 32 | 32 | Consumer<Pair<CompetitorRegistrationsPanel, AsyncCallback<Collection<CompetitorDTO>>>> registeredCompetitorsRetriever) { |
| 33 | 33 | super(stringMessages.registerCompetitors(), /* messsage */null, okButtonMessage, stringMessages.cancel(), |
| 34 | 34 | validator, callback); |
| 35 | + this.ensureDebugId("registerCompetitorsDialog"); |
|
| 35 | 36 | this.errorReporter = errorReporter; |
| 36 | 37 | this.stringMessages = stringMessages; |
| 37 | 38 | this.sailingService = sailingServiceWrite; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java
| ... | ... | @@ -57,6 +57,7 @@ public abstract class BaseQRIdentifierWidget implements IsWidget { |
| 57 | 57 | error = new Label(); |
| 58 | 58 | error.setStyleName("errorLabel"); |
| 59 | 59 | url = new Label(); |
| 60 | + url.ensureDebugId("QRIdentifierURL"); |
|
| 60 | 61 | VerticalPanel panel = new VerticalPanel(); |
| 61 | 62 | panel.add(inputGrid); |
| 62 | 63 | panel.add(qrCodeComposite); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorRegistrationsPanel.java
| ... | ... | @@ -111,6 +111,7 @@ public class CompetitorRegistrationsPanel extends FlowPanel implements BusyDispl |
| 111 | 111 | } |
| 112 | 112 | }); |
| 113 | 113 | addCompetitorWithBoatButton = new Button(stringMessages.add(stringMessages.competitorWithBoat())); |
| 114 | + addCompetitorWithBoatButton.ensureDebugId("addCompetitorWithBoatButton"); |
|
| 114 | 115 | addCompetitorWithBoatButton.addClickHandler(new ClickHandler() { |
| 115 | 116 | @Override |
| 116 | 117 | public void onClick(ClickEvent event) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorToBoatMappingsDialog.java
| ... | ... | @@ -69,6 +69,7 @@ public class CompetitorToBoatMappingsDialog extends DataEntryDialog<Map<Competit |
| 69 | 69 | this.competitorTable = new CompactCompetitorTableWrapper<>(sailingServiceWrite, stringMessages, errorReporter, |
| 70 | 70 | /* multiSelection */ false, /* enablePager */ true, userService); |
| 71 | 71 | this.boatTable = new CompactBoatTableWrapper<>(sailingServiceWrite, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true); |
| 72 | + this.boatTable.asWidget().ensureDebugId("competitorToBoatMappingBoatTable"); |
|
| 72 | 73 | ImagesBarColumn<CompetitorDTO, CompactCompetitorConfigImagesBarCell> competitorActionColumn = new ImagesBarColumn<>(new CompactCompetitorConfigImagesBarCell(stringMessages)); |
| 73 | 74 | competitorActionColumn.setFieldUpdater(new FieldUpdater<CompetitorDTO, String>() { |
| 74 | 75 | @Override |
| ... | ... | @@ -80,6 +81,7 @@ public class CompetitorToBoatMappingsDialog extends DataEntryDialog<Map<Competit |
| 80 | 81 | } |
| 81 | 82 | }); |
| 82 | 83 | competitorTable.addColumn(competitorActionColumn, stringMessages.actions()); |
| 84 | + competitorTable.asWidget().ensureDebugId("competitorToBoatMappingCompetitorTable"); |
|
| 83 | 85 | refreshableBoatSelectionModel = boatTable.getSelectionModel(); |
| 84 | 86 | refreshableCompetitorSelectionModel = competitorTable.getSelectionModel(); |
| 85 | 87 | boatListHandler = new SelectionChangeEvent.Handler() { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java
| ... | ... | @@ -111,6 +111,7 @@ public class DeviceConfigurationDetailComposite extends Composite { |
| 111 | 111 | fillCourseAreaListBox(eventsById.get(selectedValue==null||selectedValue.isEmpty() ? null : UUID.fromString(selectedValue))); |
| 112 | 112 | }); |
| 113 | 113 | captionPanel = new CaptionPanel(stringMessages.configuration()); |
| 114 | + captionPanel.ensureDebugId("DeviceConfigurationDetailComposite"); |
|
| 114 | 115 | VerticalPanel verticalPanel = new VerticalPanel(); |
| 115 | 116 | contentPanel = new VerticalPanel(); |
| 116 | 117 | cloneButton = new Button(stringMessages.save() + " + " + stringMessages.clone()); |
| ... | ... | @@ -426,6 +427,7 @@ public class DeviceConfigurationDetailComposite extends Composite { |
| 426 | 427 | |
| 427 | 428 | private Button createQrCodeButton() { |
| 428 | 429 | Button qrCodeButton = new Button(stringMessages.qrSync()); |
| 430 | + qrCodeButton.ensureDebugId("DeviceConfigurationDetailsQrCodeButton"); |
|
| 429 | 431 | qrCodeButton.addClickHandler(new ClickHandler() { |
| 430 | 432 | @Override |
| 431 | 433 | public void onClick(ClickEvent event) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java
| ... | ... | @@ -120,6 +120,7 @@ public class DeviceConfigurationListComposite extends Composite { |
| 120 | 120 | CellTable<DeviceConfigurationWithSecurityDTO> table = new BaseCelltable<DeviceConfigurationWithSecurityDTO>( |
| 121 | 121 | /* pageSize */10000, tableResource); |
| 122 | 122 | configurationsDataProvider.addDataDisplay(table); |
| 123 | + table.ensureDebugId("DeviceConfigurationList"); |
|
| 123 | 124 | table.setWidth("100%"); |
| 124 | 125 | ListHandler<DeviceConfigurationWithSecurityDTO> columnSortHandler = new ListHandler<DeviceConfigurationWithSecurityDTO>( |
| 125 | 126 | configurationsDataProvider.getList()); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java
| ... | ... | @@ -79,6 +79,7 @@ public class DeviceConfigurationPanel extends SimplePanel implements DeviceConfi |
| 79 | 79 | HorizontalPanel deviceManagementControlPanel = new HorizontalPanel(); |
| 80 | 80 | deviceManagementControlPanel.setSpacing(5); |
| 81 | 81 | addConfigurationButton = new Button(stringMessages.addConfiguration()); |
| 82 | + addConfigurationButton.ensureDebugId("addDeviceConfigurationButton"); |
|
| 82 | 83 | addConfigurationButton.addClickHandler(new ClickHandler() { |
| 83 | 84 | @Override |
| 84 | 85 | public void onClick(ClickEvent event) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java
| ... | ... | @@ -90,6 +90,7 @@ public class DeviceConfigurationQRIdentifierDialog extends DialogBox { |
| 90 | 90 | panel.add(widget); |
| 91 | 91 | panel.add(actionPanel); |
| 92 | 92 | setWidget(panel); |
| 93 | + this.ensureDebugId("DeviceConfigurationQRIdentifierDialog"); |
|
| 93 | 94 | } |
| 94 | 95 | |
| 95 | 96 | @Override |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java
| ... | ... | @@ -171,6 +171,7 @@ public class RegattaDetailsComposite extends Composite { |
| 171 | 171 | }); |
| 172 | 172 | } |
| 173 | 173 | }); |
| 174 | + button.ensureDebugId("registrationLinkWithQRCodeOpenButton"); |
|
| 174 | 175 | grid.setWidget(row , 1, button); |
| 175 | 176 | return button; |
| 176 | 177 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogAddDeviceMappingDialog.java
| ... | ... | @@ -88,6 +88,7 @@ public class RegattaLogAddDeviceMappingDialog extends DataEntryDialogWithDateTim |
| 88 | 88 | return null; |
| 89 | 89 | } |
| 90 | 90 | }, true, callback); |
| 91 | + this.ensureDebugId("RegattaLogAddDeviceMappingDialog"); |
|
| 91 | 92 | this.stringMessages = stringMessages; |
| 92 | 93 | this.sailingService = sailingServiceWrite; |
| 93 | 94 | this.regattaRegisterSecret = regattaRegisterSecret; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java
| ... | ... | @@ -84,6 +84,7 @@ public class RegattaLogTrackingDeviceMappingsDialog extends DataEntryDialog<Void |
| 84 | 84 | final StringMessages stringMessages, final ErrorReporter errorReporter, final String leaderboardName, |
| 85 | 85 | final String regattaRegistrationSecret, DialogCallback<Void> callback) { |
| 86 | 86 | super(stringMessages.mapDevices(), /*message*/ null, stringMessages.ok(), stringMessages.cancel(), /*validator*/ null, callback); |
| 87 | + this.ensureDebugId("regattaLogTrackingDeviceMappingsDialog"); |
|
| 87 | 88 | this.stringMessages = stringMessages; |
| 88 | 89 | this.sailingServiceWrite = sailingServiceWrite; |
| 89 | 90 | this.userService = userService; |
| ... | ... | @@ -99,6 +100,7 @@ public class RegattaLogTrackingDeviceMappingsDialog extends DataEntryDialog<Void |
| 99 | 100 | FlowPanel mainPanel = new FlowPanel(); |
| 100 | 101 | HorizontalPanel buttonPanel = new HorizontalPanel(); |
| 101 | 102 | Button addMappingButton = new Button(stringMessages.add()); |
| 103 | + addMappingButton.ensureDebugId("addMappingButton"); |
|
| 102 | 104 | addMappingButton.addClickHandler(new ClickHandler() { |
| 103 | 105 | @Override |
| 104 | 106 | public void onClick(ClickEvent event) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ResultImportUrlAddDialog.java
| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | package com.sap.sailing.gwt.ui.adminconsole; |
| 2 | 2 | |
| 3 | 3 | import com.google.gwt.user.client.rpc.AsyncCallback; |
| 4 | +import com.google.gwt.user.client.ui.FocusWidget; |
|
| 4 | 5 | import com.google.gwt.user.client.ui.TextBox; |
| 5 | 6 | import com.google.gwt.user.client.ui.Widget; |
| 6 | 7 | import com.sap.sailing.gwt.ui.client.SailingServiceAsync; |
| ... | ... | @@ -68,6 +69,11 @@ public class ResultImportUrlAddDialog extends DataEntryDialog<UrlDTO> { |
| 68 | 69 | } |
| 69 | 70 | |
| 70 | 71 | @Override |
| 72 | + protected FocusWidget getInitialFocusWidget() { |
|
| 73 | + return textBox; |
|
| 74 | + } |
|
| 75 | + |
|
| 76 | + @Override |
|
| 71 | 77 | protected Widget getAdditionalWidget() { |
| 72 | 78 | return textBox; |
| 73 | 79 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SelectNameForNewDeviceConfigurationDialog.java
| ... | ... | @@ -42,8 +42,10 @@ public class SelectNameForNewDeviceConfigurationDialog extends DataEntryDialog<S |
| 42 | 42 | Validator<String> validator, com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<String> callback) { |
| 43 | 43 | super(stringMessages.createDeviceConfiguration(), stringMessages.forWhichDeviceShouldConfigurationApply(), |
| 44 | 44 | stringMessages.create(), stringMessages.cancel(), validator, callback); |
| 45 | + this.ensureDebugId("SelectNameForNewDeviceConfigurationDialog"); |
|
| 45 | 46 | this.stringMessages = stringMessages; |
| 46 | 47 | this.identifierBox = createTextBox(""); |
| 48 | + this.identifierBox.ensureDebugId("raceManagerDeviceName"); |
|
| 47 | 49 | } |
| 48 | 50 | |
| 49 | 51 | @Override |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java
| ... | ... | @@ -37,6 +37,7 @@ import com.sap.sailing.domain.common.dto.BoatClassDTO; |
| 37 | 37 | import com.sap.sailing.domain.common.impl.MeterDistance; |
| 38 | 38 | import com.sap.sailing.gwt.ui.adminconsole.StructureImportListComposite.RegattaStructureProvider; |
| 39 | 39 | import com.sap.sailing.gwt.ui.adminconsole.places.AdminConsoleView.Presenter; |
| 40 | +import com.sap.sailing.gwt.ui.adminconsole.swisstiming.SwissTimingEventIdUrlUtil; |
|
| 40 | 41 | import com.sap.sailing.gwt.ui.client.Displayer; |
| 41 | 42 | import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync; |
| 42 | 43 | import com.sap.sailing.gwt.ui.client.StringMessages; |
| ... | ... | @@ -131,8 +132,8 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat |
| 131 | 132 | eventIDTextBox.addChangeHandler(new ChangeHandler() { |
| 132 | 133 | @Override |
| 133 | 134 | public void onChange(ChangeEvent event) { |
| 134 | - jsonURLTextBox.setText("http://manage2sail.com/api/public/links/event/" + eventIDTextBox.getValue() |
|
| 135 | - + "?accesstoken=bDAv8CwsTM94ujZ&mediaType=json"); |
|
| 135 | + final String url = SwissTimingEventIdUrlUtil.getUrlFromEventId(eventIDTextBox.getValue()); |
|
| 136 | + jsonURLTextBox.setText(url); |
|
| 136 | 137 | } |
| 137 | 138 | }); |
| 138 | 139 | eventIDTextBox.ensureDebugId("eventIDTextBox"); |
| ... | ... | @@ -140,8 +141,7 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat |
| 140 | 141 | jsonURLTextBox = new TextBox(); |
| 141 | 142 | jsonURLTextBox.ensureDebugId("JsonURLTextBox"); |
| 142 | 143 | jsonURLTextBox.setVisibleLength(100); |
| 143 | - jsonURLTextBox.getElement().setPropertyString("placeholder", |
|
| 144 | - "http://manage2sail.com/api/public/links/event/d30883d3-2876-4d7e-af49-891af6cbae1b?accesstoken=bDAv8CwsTM94ujZ&mediaType=json"); |
|
| 144 | + jsonURLTextBox.getElement().setPropertyString("placeholder", SwissTimingEventIdUrlUtil.getUrlFromEventId("d30883d3-2876-4d7e-af49-891af6cbae1b")); |
|
| 145 | 145 | listRegattasButton = new Button(this.stringMessages.listRegattas()); |
| 146 | 146 | importDetailsButton = new Button(this.stringMessages.importRegattas()); |
| 147 | 147 | importDetailsButton.setEnabled(false); |
| ... | ... | @@ -258,14 +258,13 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat |
| 258 | 258 | if (eventIDTextBox.getValue() == null || eventIDTextBox.getValue().length() == 0) { |
| 259 | 259 | jsonURL = jsonURLTextBox.getValue(); |
| 260 | 260 | } else { |
| 261 | - jsonURL = "http://manage2sail.com/api/public/links/event/" + eventIDTextBox.getValue() |
|
| 262 | - + "?accesstoken=bDAv8CwsTM94ujZ&mediaType=json"; |
|
| 261 | + jsonURL = SwissTimingEventIdUrlUtil.getUrlFromEventId(eventIDTextBox.getValue()); |
|
| 263 | 262 | } |
| 264 | 263 | if (jsonURL == null || jsonURL.length() == 0) { |
| 265 | 264 | errorReporter.reportError(stringMessages.pleaseEnterNonEmptyUrl()); |
| 266 | 265 | } else { |
| 267 | 266 | busyIndicator.setBusy(true); |
| 268 | - sailingServiceWrite.getRegattas(jsonURL, new AsyncCallback<Iterable<RegattaDTO>>() { |
|
| 267 | + sailingServiceWrite.getManage2SailRegattas(jsonURL, new AsyncCallback<Iterable<RegattaDTO>>() { |
|
| 269 | 268 | @Override |
| 270 | 269 | public void onFailure(Throwable caught) { |
| 271 | 270 | busyIndicator.setBusy(false); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java
| ... | ... | @@ -435,7 +435,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane |
| 435 | 435 | if (selectedRegatta != null) { |
| 436 | 436 | regattaIdentifier = new RegattaName(selectedRegatta.getName()); |
| 437 | 437 | } |
| 438 | - |
|
| 439 | 438 | // Check if the assigned regatta makes sense |
| 440 | 439 | if (checkBoatClassOK(selectedRegatta, selectedRaces)) { |
| 441 | 440 | sailingServiceWrite.trackWithSwissTiming(/* regattaToAddTo */ regattaIdentifier, selectedRaces, hostname, port==null?0:port, |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/swisstiming/SwissTimingConnectionTableWrapper.java
| ... | ... | @@ -66,7 +66,6 @@ public class SwissTimingConnectionTableWrapper extends |
| 66 | 66 | this.stringMessagesClient = stringMessages; |
| 67 | 67 | this.sailingServiceAsync = sailingServiceWriteAsync; |
| 68 | 68 | final ListHandler<SwissTimingConfigurationWithSecurityDTO> swissTimingConectionColumnListHandler = getColumnSortHandler(); |
| 69 | - |
|
| 70 | 69 | // table |
| 71 | 70 | final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionNameColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>( |
| 72 | 71 | dto -> dto.getName(), swissTimingConectionColumnListHandler); |
| ... | ... | @@ -85,7 +84,6 @@ public class SwissTimingConnectionTableWrapper extends |
| 85 | 84 | dto -> dto.getUpdateUsername(), swissTimingConectionColumnListHandler); |
| 86 | 85 | final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionCreatorNameColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>( |
| 87 | 86 | dto -> dto.getCreatorName(), swissTimingConectionColumnListHandler); |
| 88 | - |
|
| 89 | 87 | final HasPermissions type = SecuredDomainType.SWISS_TIMING_ACCOUNT; |
| 90 | 88 | final AccessControlledActionsColumn<SwissTimingConfigurationWithSecurityDTO, DefaultActionsImagesBarCell> actionColumn = create( |
| 91 | 89 | new DefaultActionsImagesBarCell(stringMessages), userService); |
| ... | ... | @@ -113,7 +111,6 @@ public class SwissTimingConnectionTableWrapper extends |
| 113 | 111 | } |
| 114 | 112 | }, userService, errorReporter).show(); |
| 115 | 113 | }); |
| 116 | - |
|
| 117 | 114 | actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_DELETE, DefaultActions.DELETE, dto -> { |
| 118 | 115 | sailingServiceWriteAsync.deleteSwissTimingConfigurations(Collections.singletonList(dto), new AsyncCallback<Void>() { |
| 119 | 116 | @Override |
| ... | ... | @@ -127,19 +124,16 @@ public class SwissTimingConnectionTableWrapper extends |
| 127 | 124 | } |
| 128 | 125 | }); |
| 129 | 126 | }); |
| 130 | - |
|
| 131 | 127 | final EditOwnershipDialog.DialogConfig<SwissTimingConfigurationWithSecurityDTO> configOwnership = EditOwnershipDialog |
| 132 | 128 | .create(userService.getUserManagementWriteService(), type, dto -> refreshSwissTimingConnectionList(), |
| 133 | 129 | stringMessages); |
| 134 | - |
|
| 135 | 130 | final EditACLDialog.DialogConfig<SwissTimingConfigurationWithSecurityDTO> configACL = EditACLDialog.create( |
| 136 | 131 | userService.getUserManagementWriteService(), type, dto -> dto.getAccessControlList(), stringMessages); |
| 137 | - |
|
| 138 | 132 | actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_OWNERSHIP, DefaultActions.CHANGE_OWNERSHIP, |
| 139 | 133 | configOwnership::openOwnershipDialog); |
| 140 | 134 | actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_ACL, DefaultActions.CHANGE_ACL, |
| 141 | 135 | u -> configACL.openDialog(u)); |
| 142 | - |
|
| 136 | + // filter field: |
|
| 143 | 137 | filterField = new LabeledAbstractFilterablePanel<SwissTimingConfigurationWithSecurityDTO>( |
| 144 | 138 | new Label(stringMessagesClient.filterSwissTimingConnections()), |
| 145 | 139 | new ArrayList<SwissTimingConfigurationWithSecurityDTO>(), dataProvider, stringMessages) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/swisstiming/SwissTimingEventIdUrlUtil.java
| ... | ... | @@ -2,8 +2,9 @@ package com.sap.sailing.gwt.ui.adminconsole.swisstiming; |
| 2 | 2 | |
| 3 | 3 | public final class SwissTimingEventIdUrlUtil { |
| 4 | 4 | private static final String EVENT_ID_PATTERN = "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}"; |
| 5 | - private static final String MANAGE2SAIL_API_BASE_URL = "http://manage2sail.com/api/public/links/event/"; |
|
| 6 | - private static final String MANAGE2SAIL_API_ACCESS_TOKEN = "?accesstoken=bDAv8CwsTM94ujZ"; |
|
| 5 | + private static final String MANAGE2SAIL_API_BASE_URL = "https://manage2sail.com/api/public/links/event/"; |
|
| 6 | + private static final String MANAGE2SAIL_API_ACCESS_TOKEN = "bDAv8CwsTM94ujZ"; |
|
| 7 | + private static final String MANAGE2SAIL_API_ACCESS_TOKEN_URL_PARAM = "?accesstoken="+MANAGE2SAIL_API_ACCESS_TOKEN; |
|
| 7 | 8 | private static final String MANAGE2SAIL_API_APPENDIX = "&mediaType=json&includeRaces=true"; |
| 8 | 9 | |
| 9 | 10 | /** |
| ... | ... | @@ -13,7 +14,7 @@ public final class SwissTimingEventIdUrlUtil { |
| 13 | 14 | * @return The event ID inferred from the Json Url. |
| 14 | 15 | */ |
| 15 | 16 | public static String getEventIdFromUrl(String eventUrl) { |
| 16 | - if (eventUrl.matches("http://manage2sail.com/.*" + EVENT_ID_PATTERN + ".*")) { |
|
| 17 | + if (eventUrl.matches("https?://manage2sail.com/.*" + EVENT_ID_PATTERN + ".*")) { |
|
| 17 | 18 | return eventUrl.replaceFirst(".*(" + EVENT_ID_PATTERN + ").*", "$1"); |
| 18 | 19 | } |
| 19 | 20 | return null; |
| ... | ... | @@ -27,7 +28,7 @@ public final class SwissTimingEventIdUrlUtil { |
| 27 | 28 | public static String getUrlFromEventId(String eventIdText) { |
| 28 | 29 | if (eventIdText.matches(".*" + EVENT_ID_PATTERN + ".*")) { |
| 29 | 30 | final String inferredEventId = eventIdText.replaceFirst(".*(" + EVENT_ID_PATTERN + ").*", "$1"); |
| 30 | - return MANAGE2SAIL_API_BASE_URL + inferredEventId + MANAGE2SAIL_API_ACCESS_TOKEN + MANAGE2SAIL_API_APPENDIX; |
|
| 31 | + return MANAGE2SAIL_API_BASE_URL + inferredEventId + MANAGE2SAIL_API_ACCESS_TOKEN_URL_PARAM + MANAGE2SAIL_API_APPENDIX; |
|
| 31 | 32 | } |
| 32 | 33 | return null; |
| 33 | 34 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java
| ... | ... | @@ -380,8 +380,6 @@ public class DetailTypeFormatter { |
| 380 | 380 | return stringMessages.currentHeelInDegree(); |
| 381 | 381 | case BRAVO_RACE_PITCH_IN_DEGREES: |
| 382 | 382 | return stringMessages.currentPitchInDegree(); |
| 383 | - default: |
|
| 384 | - break; |
|
| 385 | 383 | } |
| 386 | 384 | return detailType.name(); |
| 387 | 385 | } |
| ... | ... | @@ -597,8 +595,6 @@ public class DetailTypeFormatter { |
| 597 | 595 | case EXPEDITION_LEG_JIB_CAR_STBD: |
| 598 | 596 | case EXPEDITION_LEG_MAST_BUTT: |
| 599 | 597 | return ""; |
| 600 | - default: |
|
| 601 | - break; |
|
| 602 | 598 | } |
| 603 | 599 | // Throwing an exception to get notified if an implementation of |
| 604 | 600 | // a detail type is missing. |
| ... | ... | @@ -852,11 +848,55 @@ public class DetailTypeFormatter { |
| 852 | 848 | case EXPEDITION_RACE_TWS: |
| 853 | 849 | case EXPEDITION_RACE_VMG: |
| 854 | 850 | case EXPEDITION_RACE_VMG_TARG_VMG_DELTA: |
| 851 | + case BRAVO_RACE_HEEL_IN_DEGREES: |
|
| 852 | + case BRAVO_RACE_PITCH_IN_DEGREES: |
|
| 853 | + case EXPEDITION_LEG_AWA: |
|
| 854 | + case EXPEDITION_LEG_AWS: |
|
| 855 | + case EXPEDITION_LEG_BARO: |
|
| 856 | + case EXPEDITION_LEG_BOAT_SPEED: |
|
| 857 | + case EXPEDITION_LEG_COG: |
|
| 858 | + case EXPEDITION_LEG_COURSE: |
|
| 859 | + case EXPEDITION_LEG_DISTANCE_BELOW_LINE: |
|
| 860 | + case EXPEDITION_LEG_DISTANCE_TO_COMMITTEE_BOAT: |
|
| 861 | + case EXPEDITION_LEG_DISTANCE_TO_PIN: |
|
| 862 | + case EXPEDITION_LEG_DIST_TO_PORT_LAYLINE: |
|
| 863 | + case EXPEDITION_LEG_DIST_TO_STB_LAYLINE: |
|
| 864 | + case EXPEDITION_LEG_FORESTAY_LOAD: |
|
| 865 | + case EXPEDITION_LEG_HEADING: |
|
| 866 | + case EXPEDITION_LEG_JIB_CAR_PORT: |
|
| 867 | + case EXPEDITION_LEG_JIB_CAR_STBD: |
|
| 868 | + case EXPEDITION_LEG_LINE_SQUARE_FOR_WIND_DIRECTION: |
|
| 869 | + case EXPEDITION_LEG_LOAD_P: |
|
| 870 | + case EXPEDITION_LEG_LOAD_S: |
|
| 871 | + case EXPEDITION_LEG_MAST_BUTT: |
|
| 872 | + case EXPEDITION_LEG_RAKE: |
|
| 873 | + case EXPEDITION_LEG_RATE_OF_TURN: |
|
| 874 | + case EXPEDITION_LEG_RUDDER_ANGLE: |
|
| 875 | + case EXPEDITION_LEG_SOG: |
|
| 876 | + case EXPEDITION_LEG_TARGET_HEEL: |
|
| 877 | + case EXPEDITION_LEG_TARG_BOAT_SPEED: |
|
| 878 | + case EXPEDITION_LEG_TARG_TWA: |
|
| 879 | + case EXPEDITION_LEG_TIME_TO_BURN_TO_COMMITTEE_BOAT: |
|
| 880 | + case EXPEDITION_LEG_TIME_TO_BURN_TO_LINE: |
|
| 881 | + case EXPEDITION_LEG_TIME_TO_BURN_TO_PIN: |
|
| 882 | + case EXPEDITION_LEG_TIME_TO_COMMITTEE_BOAT: |
|
| 883 | + case EXPEDITION_LEG_TIME_TO_GUN: |
|
| 884 | + case EXPEDITION_LEG_TIME_TO_PIN: |
|
| 885 | + case EXPEDITION_LEG_TIME_TO_PORT_LAYLINE: |
|
| 886 | + case EXPEDITION_LEG_TIME_TO_STB_LAYLINE: |
|
| 887 | + case EXPEDITION_LEG_TWA: |
|
| 888 | + case EXPEDITION_LEG_TWD: |
|
| 889 | + case EXPEDITION_LEG_TWS: |
|
| 890 | + case EXPEDITION_LEG_VMG: |
|
| 891 | + case EXPEDITION_LEG_VMG_TARG_VMG_DELTA: |
|
| 892 | + case EXPEDITION_RACE_BARO: |
|
| 893 | + case EXPEDITION_RACE_JIB_CAR_PORT: |
|
| 894 | + case EXPEDITION_RACE_JIB_CAR_STBD: |
|
| 895 | + case EXPEDITION_RACE_LOAD_P: |
|
| 896 | + case EXPEDITION_RACE_LOAD_S: |
|
| 897 | + case EXPEDITION_RACE_MAST_BUTT: |
|
| 855 | 898 | return ""; |
| 856 | - default: |
|
| 857 | - break; |
|
| 858 | 899 | } |
| 859 | - |
|
| 860 | 900 | return ""; |
| 861 | 901 | } |
| 862 | 902 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
| ... | ... | @@ -52,8 +52,8 @@ import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImp |
| 52 | 52 | import com.sap.sailing.domain.common.windfinder.SpotDTO; |
| 53 | 53 | import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; |
| 54 | 54 | import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; |
| 55 | -import com.sap.sailing.gwt.ui.shared.BearingWithConfidenceDTO; |
|
| 56 | 55 | import com.sap.sailing.gwt.ui.shared.AccountWithSecurityDTO; |
| 56 | +import com.sap.sailing.gwt.ui.shared.BearingWithConfidenceDTO; |
|
| 57 | 57 | import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; |
| 58 | 58 | import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; |
| 59 | 59 | import com.sap.sailing.gwt.ui.shared.CompetitorProviderDTO; |
| ... | ... | @@ -364,7 +364,7 @@ public interface SailingService extends RemoteService, RemoteReplicationService |
| 364 | 364 | * @param manage2SailJsonUrl |
| 365 | 365 | * the URL pointing to a Manage2Sail JSON document that contains the link to the XRR document |
| 366 | 366 | */ |
| 367 | - Iterable<RegattaDTO> getRegattas(String manage2SailJsonUrl) throws UnauthorizedException; |
|
| 367 | + Iterable<RegattaDTO> getManage2SailRegattas(String manage2SailJsonUrl) throws Exception; |
|
| 368 | 368 | |
| 369 | 369 | boolean doesRegattaLogContainCompetitors(String name) |
| 370 | 370 | throws UnauthorizedException, DoesNotHaveRegattaLogException, NotFoundException; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
| ... | ... | @@ -387,12 +387,13 @@ public interface SailingServiceAsync extends RemoteReplicationServiceAsync { |
| 387 | 387 | void getEventsForLeaderboard(String leaderboardName, AsyncCallback<Collection<EventDTO>> callback); |
| 388 | 388 | |
| 389 | 389 | /** |
| 390 | - * Imports regatta structure definitions from an ISAF XRR document |
|
| 390 | + * Imports regatta structure definitions from an ISAF XRR document provided through SwissTiming's Manage2Sail. |
|
| 391 | + * The back-end will enhance the URL with the necessary API access token if none is provided in the URL. |
|
| 391 | 392 | * |
| 392 | 393 | * @param manage2SailJsonUrl |
| 393 | 394 | * the URL pointing to a Manage2Sail JSON document that contains the link to the XRR document |
| 394 | 395 | */ |
| 395 | - void getRegattas(String manage2SailJsonUrl, AsyncCallback<Iterable<RegattaDTO>> asyncCallback); |
|
| 396 | + void getManage2SailRegattas(String manage2SailJsonUrl, AsyncCallback<Iterable<RegattaDTO>> asyncCallback); |
|
| 396 | 397 | |
| 397 | 398 | /** |
| 398 | 399 | * Returns mark passings for the competitor. Using the {@code waitForCalculations} parameter callers can control |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
| ... | ... | @@ -62,7 +62,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, |
| 62 | 62 | String leaderboardName(); |
| 63 | 63 | String pleaseEnterAName(); |
| 64 | 64 | String pleaseEnterABoatClass(); |
| 65 | - String discardRacesFromHowManyStartedRacesOn(); |
|
| 65 | + String applyDiscardsBasedOnNumberOfScoredRaces(int maxNumberOfDiscardedResults); |
|
| 66 | 66 | String leaderboardWithThisNameAlreadyExists(); |
| 67 | 67 | String discardThresholdsMustBeNumeric(); |
| 68 | 68 | String discardThresholdsMustBeAscending(); |
| ... | ... | @@ -2078,6 +2078,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, |
| 2078 | 2078 | String qrCodeTitle(String leaderboardName); |
| 2079 | 2079 | String qrCodeSubtitle(String eventName, String eventLocationAndVenue); |
| 2080 | 2080 | String qrCodeTitleOpenRegatta(String regattaName); |
| 2081 | + String qrCodeTitleOpenRaceManagerApp(); |
|
| 2082 | + String qrCodeSubTitleOpenRaceManagerApp(); |
|
| 2083 | + String qrCodeTitleErrorCreatingQrCode(); |
|
| 2081 | 2084 | String qrCodeUnsecureServerRedirect(String hostName); |
| 2082 | 2085 | String qrCodeUnsecureServerRedirectTitle(String hostName); |
| 2083 | 2086 | String qrCodeInfoMessage(); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Unlink |
| 52 | 52 | leaderboardName=Leaderboard Name |
| 53 | 53 | pleaseEnterAName=Please enter a name |
| 54 | 54 | pleaseEnterABoatClass=Please enter a boat class |
| 55 | -discardRacesFromHowManyStartedRacesOn=Discard one or more races, starting with how many started races there are |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Configure discards. Enter into box X, the number of scored races required to apply the Xth discard. Maximum discards: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Leaderboard with this name already exists |
| 58 | 57 | discardThresholdsMustBeNumeric=Discard thresholds must be numeric |
| ... | ... | @@ -2114,6 +2113,9 @@ qrCodeTitleBouy=Welcome as a bouy tender for the regatta {0} |
| 2114 | 2113 | qrCodeTitle=Welcome to the regatta {0} |
| 2115 | 2114 | qrCodeSubtitle={0} - {1} |
| 2116 | 2115 | qrCodeTitleOpenRegatta=Welcome to the public regatta {0}! |
| 2116 | +qrCodeTitleOpenRaceManagerApp=Welcome to the Race Manager App registration |
|
| 2117 | +qrCodeSubTitleOpenRaceManagerApp=The Race Manager app is only available on Android devices |
|
| 2118 | +qrCodeTitleErrorCreatingQrCode=Data is incorrect or out of date. Cannot create QR-Code. |
|
| 2117 | 2119 | qrCodeUnsecureServerRedirect=The given link will redirect to the unknown site {0}, confirm to navigate there |
| 2118 | 2120 | qrCodeUnsecureServerRedirectTitle={0} Unknown server |
| 2119 | 2121 | qrCodeInfoMessage=Please scan this QR Code with your mobile device to proceed with the registration |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_cs.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Odpojit |
| 52 | 52 | leaderboardName=Název výsledkové tabule |
| 53 | 53 | pleaseEnterAName=Zadejte název |
| 54 | 54 | pleaseEnterABoatClass=Zadejte lodní třídu |
| 55 | -discardRacesFromHowManyStartedRacesOn=Vyřadit jednu nebo více rozjížděk počínaje počtem odstartovaných rozjížděk |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Nakonfigurujte vyřazení. Zadejte do pole X počet bodovaných rozjížděk vyžadovaných k použití X-tého vyřazení. Maximum vyřazení: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Výsledková tabule s tímto názvem již existuje |
| 58 | 57 | discardThresholdsMustBeNumeric=Prahy vyřazení musejí být numerické |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_da.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Fjern tilknytning |
| 52 | 52 | leaderboardName=Ranglistenavn |
| 53 | 53 | pleaseEnterAName=Indtast et navn |
| 54 | 54 | pleaseEnterABoatClass=Indtast en bådklasse |
| 55 | -discardRacesFromHowManyStartedRacesOn=Opgiv en eller flere kapsejladser, startende med hvor mange startede kapsejladser der er |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Konfigurer opgivelser. Indtast i felt X antallet af pointgivne kapsejladser, der kræves for at anvende den X''te opgivelse. Maks. antal opgivelser: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Rangliste med dette navn findes allerede |
| 58 | 57 | discardThresholdsMustBeNumeric=Opgivelsesgrænser skal være numeriske |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
| ... | ... | @@ -22,7 +22,7 @@ racesAndTracking=Wettfahrten/Tracking |
| 22 | 22 | tracked=getracked |
| 23 | 23 | time=Zeit |
| 24 | 24 | linkedRaces=Verknüpfte Rennen |
| 25 | -timeTooltip=Zeit, die der Teilnehmer auf diesem Schenkel gesegelt ist. Beim ersten Schenkel zählt die Zeit ab Überquerung der Startlinie. |
|
| 25 | +timeTooltip=Zeit, die der Teilnehmer auf diesem Bahnschenkel gesegelt ist. Beim ersten Bahnschenkel zählt die Zeit ab Überquerung der Startlinie. |
|
| 26 | 26 | playSpeed=Abspielgeschwindigkeit |
| 27 | 27 | playSpeedHelp="1" für normale Abspielgeschwindigkeit, "2" für doppelte Geschwindigkeit usw. |
| 28 | 28 | playModeLive=Live |
| ... | ... | @@ -52,7 +52,6 @@ unlink=Unlink |
| 52 | 52 | leaderboardName=Name der Rangliste |
| 53 | 53 | pleaseEnterAName=Bitte geben Sie einen Namen ein. |
| 54 | 54 | pleaseEnterABoatClass=Bitte geben Sie eine Bootsklasse ein. |
| 55 | -discardRacesFromHowManyStartedRacesOn=Discard one more race starting with how many started races |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Streicher konfigurieren: in Feld X die Anzahl gewerteter Wettfahrten eingeben, ab welcher der X-te Streicher angewendet werden soll. Maximal {0} Streicher. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Eine Rangliste mit diesem Namen existiert bereits. |
| 58 | 57 | discardThresholdsMustBeNumeric=Discard thresholds must be numeric |
| ... | ... | @@ -77,10 +76,10 @@ expand=+ |
| 77 | 76 | leg=L |
| 78 | 77 | distanceInMeters=Distanz |
| 79 | 78 | averageSpeedInKnots=\u2205 FüG |
| 80 | -averageSpeedInKnotsTooltip=Die durchschnittliche Fahrt über Grund auf diesem Schenkel,\nberechnet durch die Division der gefahrenen Distanz durch die benötigte Zeit. |
|
| 79 | +averageSpeedInKnotsTooltip=Die durchschnittliche Fahrt über Grund auf diesem Bahnschenkel,\nberechnet durch die Division der gefahrenen Distanz durch die benötigte Zeit. |
|
| 81 | 80 | raceAverageSpeedInKnotsTooltip=Die durchschnittliche Fahrt über Grund in diesem Rennen,\nberechnet durch die Division der gefahrenen Distanz durch die benötigte Zeit. |
| 82 | 81 | rankGain=Platz +/- |
| 83 | -rankGainTooltip=Die Differenz zwischen der Platzierung im aktuellen und im vorangegangenen Schenkel |
|
| 82 | +rankGainTooltip=Die Differenz zwischen der Platzierung im aktuellen und im vorangegangenen Bahnschenkel |
|
| 84 | 83 | sapSailingAnalytics=Sailing Analytics |
| 85 | 84 | leaderboard=Rangliste |
| 86 | 85 | leaderboards=Ranglisten |
| ... | ... | @@ -96,7 +95,7 @@ gapToLeaderInSecondsTooltip=Schätzt die (berechnete) Ankunftszeit an der jetzig |
| 96 | 95 | velocityMadeGoodInKnots=VMG |
| 97 | 96 | velocityMadeGoodInKnotsTooltip=Die effektive Geschwindigkeit zum Ziel; auf der Kreuz die windwärtige Geschwindigkeit; auf der Vorwindstrecke die leewärtige Geschwindigkeit |
| 98 | 97 | windwardDistanceToGoInMeters=Abstand in Windrichtung zur nächsten Bahnmarke |
| 99 | -windwardDistanceToGoInMetersTooltip=Windwärtige / leewärtige Distanz des Teilnehmerns bis zum Ende des Schenkels auf Kreuz bzw. Vorwindstrecke; Strecke entlang des Schenkels bei Holeschlägen |
|
| 98 | +windwardDistanceToGoInMetersTooltip=Windwärtige / leewärtige Distanz des Teilnehmerns bis zum Ende des Schenkels auf Kreuz bzw. Vorwindstrecke; Strecke entlang des Bahnschenkels bei Holeschlägen |
|
| 100 | 99 | windwardDistanceToCompetitorFarthestAheadInMeters=Rückstand |
| 101 | 100 | windwardDistanceToCompetitorFarthestAheadInMetersTooltip=Windwärtiger Abstand zum Boot, das bisher am weitesten windwärts gesegelt ist.\nFür Handicap-Wertungen ist das nicht notwendigerweise der nach Verrechnung in Führung liegende Teilnehmer. |
| 102 | 101 | name=Name |
| ... | ... | @@ -152,7 +151,7 @@ timing=Timing |
| 152 | 151 | raceDetailsToShow=Renndetails |
| 153 | 152 | tracking=Tracking |
| 154 | 153 | overallDetailsToShow=Regatta-Details |
| 155 | -legDetailsToShow=Schenkeldetails |
|
| 154 | +legDetailsToShow=Bahnschenkeldetails |
|
| 156 | 155 | showCarryColumn=Spalte Übertrag |
| 157 | 156 | showCarryColumnCheckbox=Spalte Übertrag anzeigen |
| 158 | 157 | showCarryColumnTitle=Spalte Übertrag anzeigen |
| ... | ... | @@ -166,7 +165,7 @@ selectedRaces=Welche Rennen möchten Sie angezeigt bekommen? |
| 166 | 165 | lengthInSeconds=Länge (s) |
| 167 | 166 | stressTest=Stresstest |
| 168 | 167 | numberOfManeuvers=Manöver |
| 169 | -numberOfManeuversTooltip=Die Anzahl der gesegelten Manöver in diesem Schenkel |
|
| 168 | +numberOfManeuversTooltip=Die Anzahl der gesegelten Manöver in diesem Bahnschenkel |
|
| 170 | 169 | numberOfManeuversInRaceTooltip=Die Anzahl der gesegelten Manöver in diesem Rennen |
| 171 | 170 | tacks=Wenden |
| 172 | 171 | jibes=Halsen |
| ... | ... | @@ -176,7 +175,7 @@ maneuverTypes=Manöver |
| 176 | 175 | chooseChart=Diagramm wählen |
| 177 | 176 | raceNameEmpty=Rennname leer |
| 178 | 177 | distanceTraveled=Zurückgelegte Strecke |
| 179 | -distanceTraveledTooltip=Die zurückgelegt Strecke vom Start des Schenkels bis zum Ende\noder bis zum aktuellen Zeitpunkt, falls der Schenkel noch nicht beendet ist. |
|
| 178 | +distanceTraveledTooltip=Die zurückgelegt Strecke vom Start des Schenkels bis zum Ende\noder bis zum aktuellen Zeitpunkt, falls der Bahnschenkel noch nicht beendet ist. |
|
| 180 | 179 | raceDistanceTraveledTooltip=Die zurückgelegte Strecke vom Start des Rennens, bis zum Ende\noder bis zum aktuellen Zeitpunkt, falls das Rennen noch nicht beendet ist. |
| 181 | 180 | velocityMadeGoodLong=Velocity Made Good |
| 182 | 181 | speedOverGroundLong=Speed Over Ground |
| ... | ... | @@ -298,9 +297,9 @@ leaderboardGroups=Ranglisten-Gruppen |
| 298 | 297 | filterLeaderboardGroupsByName=Ranglisten-Gruppen nach Name filtern |
| 299 | 298 | createNewLeaderboardGroup=Neue Ranglisten-Gruppe erstellen |
| 300 | 299 | leaderboardGroupConfiguration=Ranglistengruppen-Konfiguration |
| 301 | -legs=Schenkel |
|
| 302 | -currentLeg=Aktueller Schenkel |
|
| 303 | -currentLegTooltip=Schenkel auf dem sich der Teilnehmer aktuell befindet |
|
| 300 | +legs=Bahnschenkel |
|
| 301 | +currentLeg=Aktueller Bahnschenkel |
|
| 302 | +currentLegTooltip=Bahnschenkel auf dem sich der Teilnehmer aktuell befindet |
|
| 304 | 303 | leaderboardGroup=Ranglisten-Gruppe |
| 305 | 304 | pleaseEnterNonEmptyDescription=Bitte eine nicht-leere Beschreibung eingeben |
| 306 | 305 | groupWithThisNameAlreadyExists=Ranglisten-Gruppe mit diesem Namen existiert bereits |
| ... | ... | @@ -367,11 +366,11 @@ replication=Replikation |
| 367 | 366 | averageAbsoluteCrossTrackErrorInMeters=\u2205 XTE |
| 368 | 367 | averageAbsoluteCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Schenkel; die Seite, zu der der Teilnehmer abweicht, spielt dabei keine Rolle; aktueller Wert während des Bahnschenkels, danach der Durchschnittswert. |
| 369 | 368 | averageSignedCrossTrackErrorInMeters=\u2205 XTE +/- |
| 370 | -averageSignedCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Schenkel, wobei negative Werte Abweichungen nach links, positive Werte Abweichungen nach rechts bedeuten; aktueller Wert während des Bahnschenkels, danach der Durchschnittswert. |
|
| 369 | +averageSignedCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Bahnschenkel, wobei negative Werte Abweichungen nach links, positive Werte Abweichungen nach rechts bedeuten; aktueller Wert während des Bahnschenkels, danach der Durchschnittswert. |
|
| 371 | 370 | currentAbsoluteCrossTrackErrorInMeters=\u2205 XTE |
| 372 | -currentAbsoluteCrossTrackErrorInMetersTooltip=Die momentane Querabweichung zur Kursmittellinie in diesem Schenkel; die Seite, zu der der Teilnehmer abweicht, spielt dabei keine Rolle; nur verfügbar, wenn der Teilnehmer zum angefragten Zeitpunkt in einem/dem Bahnschenkel segelt. |
|
| 371 | +currentAbsoluteCrossTrackErrorInMetersTooltip=Die momentane Querabweichung zur Kursmittellinie in diesem Bahnschenkel; die Seite, zu der der Teilnehmer abweicht, spielt dabei keine Rolle; nur verfügbar, wenn der Teilnehmer zum angefragten Zeitpunkt in einem/dem Bahnschenkel segelt. |
|
| 373 | 372 | currentSignedCrossTrackErrorInMeters=\u2205 XTE +/- |
| 374 | -currentSignedCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Schenkel, wobei negative Werte Abweichungen nach links, positive Werte Abweichungen nach rechts bedeuten; nur verfügbar, wenn der Teilnehmer zum angefragten Zeitpunkt in einem/dem Bahnschenkel segelt. |
|
| 373 | +currentSignedCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Bahnschenkel, wobei negative Werte Abweichungen nach links, positive Werte Abweichungen nach rechts bedeuten; nur verfügbar, wenn der Teilnehmer zum angefragten Zeitpunkt in einem/dem Bahnschenkel segelt. |
|
| 375 | 374 | raceAverageAbsoluteCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie für alle Kreuzen in diesem Rennen; die Seite, zu der der Teilnehmer abweicht, spielt dabei keine Rolle; aktueller Wert während des Bahnschenkels, danach der Durchschnittswert. |
| 376 | 375 | raceAverageSignedCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie für alle Kreuzen diesem Rennen, wobei negative Werte Abweichungen nach links, positive Werte Abweichungen nach rechts bedeuten. |
| 377 | 376 | helpLines=Hilfslinien |
| ... | ... | @@ -859,7 +858,7 @@ polarDiagramButton=Polardiagramm |
| 859 | 858 | ratioBetweenTimeSinceLastPositionFixAndAverageSamplingInterval=GPS Verzug |
| 860 | 859 | ratioBetweenTimeSinceLastPositionFixAndAverageSamplingIntervalTooltip=Verhältnis zwischen dem Alter der zuletzt empfangenen GPS-Position und dem durchschnittlichen Abtastintervall |
| 861 | 860 | trackingQuality=Tracking-Qualität |
| 862 | -legType=Art des Schenkels |
|
| 861 | +legType=Art des Bahnschenkels |
|
| 863 | 862 | sailID=Segelnummer |
| 864 | 863 | seriesLeaderboard=Rangliste der Serie |
| 865 | 864 | regattaLeaderboards=Ranglisten der Regatta |
| ... | ... | @@ -1274,7 +1273,7 @@ orMultipleEmails=oder mehrere E-Mails (Komma-separiert) |
| 1274 | 1273 | courseOverGroundTrueDegreesTooltip=Rechtweisender Kurs über Grund in Grad |
| 1275 | 1274 | courseOverGroundTrueDegrees=KüG |
| 1276 | 1275 | distanceIncludingGateStartInMeters=Distanz (m. Torstart) |
| 1277 | -distanceTraveledIncludingGateStartTooltip=Die zurückgelegt Strecke vom Start des Schenkels bis zum Ende\noder bis zum aktuellen Zeitpunkt, falls der Schenkel noch nicht beendet ist.\nEnthält der Schenkel einen Torstart, so wird die Distanz vom Pin End bis zur Startposition des Teilnehmers hinzugezählt,\num so vergleichbare Distanzen auch für Teilneher zu erhalten, die zu verschiedenen Zeiten gestartet sind. |
|
| 1276 | +distanceTraveledIncludingGateStartTooltip=Die zurückgelegt Strecke vom Start des Bahnschenkels bis zum Ende\noder bis zum aktuellen Zeitpunkt, falls der Bahnschenkel noch nicht beendet ist.\nEnthält der Bahnschenkel einen Torstart, so wird die Distanz vom Pin End bis zur Startposition des Teilnehmers hinzugezählt,\num so vergleichbare Distanzen auch für Teilneher zu erhalten, die zu verschiedenen Zeiten gestartet sind. |
|
| 1278 | 1277 | raceDistanceTraveledIncludingGateStartTooltip=Die zurückgelegte Strecke vom Start des Rennens, bis zum Ende\noder bis zum aktuellen Zeitpunkt, falls das Rennen noch nicht beendet ist. Bei einem Torstart wird die Distanz vom Pin End bis zur Startposition des Teilnehmers hinzugezählt,\num so vergleichbare Distanzen auch für Teilneher zu erhalten, die zu verschiedenen Zeiten gestartet sind. |
| 1279 | 1278 | dashboardHeader=Dashboard |
| 1280 | 1279 | dashboardNoWindBotAvailableHeader=Wind Bot nicht verfügbar. |
| ... | ... | @@ -1329,7 +1328,7 @@ eventsCount[one]={0,number} Veranstaltung |
| 1329 | 1328 | competitorsCount={0,number} Teams |
| 1330 | 1329 | courseAreaName=Regattabahn: {0} |
| 1331 | 1330 | competitorsCount[one]={0,number} Team |
| 1332 | -currentOfTotalLegs=Schenkel {0,number}/{1,number} |
|
| 1331 | +currentOfTotalLegs=Bahnschenkel {0,number}/{1,number} |
|
| 1333 | 1332 | clone=Clone |
| 1334 | 1333 | changed=Changed |
| 1335 | 1334 | matcher=Matcher |
| ... | ... | @@ -2101,6 +2100,9 @@ qrCodeTitle=Willkommen zu der Regatta {0} |
| 2101 | 2100 | qrCodeTitleBouy=Herzlich Willkommen als Tonnenleger für die Regatta {0} |
| 2102 | 2101 | qrCodeSubtitle={0} - {1} |
| 2103 | 2102 | qrCodeTitleOpenRegatta=Willkommen zur öffentlichen Regatta {0}! |
| 2103 | +qrCodeTitleOpenRaceManagerApp=Willkommen bei der Race Manager App Registrierung |
|
| 2104 | +qrCodeSubTitleOpenRaceManagerApp=Die Race Manager-App ist nur auf Android-Geräten verfügbar |
|
| 2105 | +qrCodeTitleErrorCreatingQrCode=Die Daten sind falsch oder veraltet. Der QR-Code kann nicht erstellt werden. |
|
| 2104 | 2106 | qrCodeUnsecureServerRedirect=Der gescannte Link verweist auf die unbekannte Seite: {0}, betätigen zum fortsetzen |
| 2105 | 2107 | qrCodeUnsecureServerRedirectTitle={0} ist ein unbekannter Server |
| 2106 | 2108 | qrCodeInfoMessage=Bitte den QR Code mit dem Smartphone scannen, um mit der Registrierung fortzufahren! |
| ... | ... | @@ -2248,7 +2250,7 @@ errorAssigningCertificates: Fehler beim Zuweisen von Messbriefen zu Booten: {0} |
| 2248 | 2250 | insertedAndReplacedAndRemovedCertificateAssignments=Neue Messbriefzuweisungen: {0}. Geänderte Zuweisungen: {1}. Gelöschte Zuweisungen: {2}. |
| 2249 | 2251 | spreadTotalDistanceProportionallyAcrossLegs=Gesamtdistanz proportional auf Bahnschenkel verteilen |
| 2250 | 2252 | trackedTwaInDegrees=Getrackter TWA (°) |
| 2251 | -trackedDistanceInNauticalMiles=Getrackte Schenkel-Distanz (NM) |
|
| 2253 | +trackedDistanceInNauticalMiles=Getrackte Bahnschenkel-Distanz (NM) |
|
| 2252 | 2254 | useTrackedData=Tracking-Werte verwenden |
| 2253 | 2255 | totalDistance=Gesamtdistanz (NM) |
| 2254 | 2256 | setAllLegsToType=Typ für alle Bahnschenkel setzen |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Quitar enlace |
| 52 | 52 | leaderboardName=Nombre de tabla de clasificación |
| 53 | 53 | pleaseEnterAName=Indique un nombre |
| 54 | 54 | pleaseEnterABoatClass=Indique una clase de embarcación |
| 55 | -discardRacesFromHowManyStartedRacesOn=Descarte una o más pruebas comenzando por cuántas pruebas iniciadas hay |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Configurar descartadas. Introduzca en la casilla X el número de pruebas puntuadas necesario para aplicar el descarte X. Máximo de descarte: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Ya hay una tabla de clasificación con este nombre. |
| 58 | 57 | discardThresholdsMustBeNumeric=Los umbrales de rechazo deben ser numéricos |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Annuler Lier |
| 52 | 52 | leaderboardName=Nom du palmarès |
| 53 | 53 | pleaseEnterAName=Saisissez un nom. |
| 54 | 54 | pleaseEnterABoatClass=Saisissez une catégorie de bateau. |
| 55 | -discardRacesFromHowManyStartedRacesOn=Éliminez une ou plusieurs courses, en partant du nombre de courses déjà lancées. |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Configurez les éliminations. Dans la case X, saisissez le nombre de courses (avec classement) requis pour que l''élimination au Xe s''applique. Nombre d''éliminations au maximum : {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Un palmarès de ce nom existe déjà. |
| 58 | 57 | discardThresholdsMustBeNumeric=Les seuils d''élimination doivent être numériques. |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_it.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Annulla collegamento |
| 52 | 52 | leaderboardName=Nome classifica |
| 53 | 53 | pleaseEnterAName=Inserire un nome |
| 54 | 54 | pleaseEnterABoatClass=Inserire una classe della barca |
| 55 | -discardRacesFromHowManyStartedRacesOn=Eliminare una o più gare, iniziando dal numero di gare iniziate |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Configurare le eliminazioni. Inserire nella casella X il numero di gare portate a termine richiesto per applicare la X° eliminazione. Numero massimo di eliminazioni: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Esiste già una classifica con questo nome |
| 58 | 57 | discardThresholdsMustBeNumeric=Le soglie di scarto devono essere numeriche |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=リンク解除 |
| 52 | 52 | leaderboardName=リーダーボード名 |
| 53 | 53 | pleaseEnterAName=名称を入力してください |
| 54 | 54 | pleaseEnterABoatClass=艇種を入力してください |
| 55 | -discardRacesFromHowManyStartedRacesOn=1 つ以上のレースを除外 (スタート済みレースがいくつ存在しているかから開始) |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=除外を設定します。X 番目の除外を適用するために必要な得点済みレースの数を X ボックスに入力します。最大除外数: {0}。 |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=この名称のリーダーボードは登録されています |
| 58 | 57 | discardThresholdsMustBeNumeric=破棄しきい値は数値でなければなりません |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Eliminar link |
| 52 | 52 | leaderboardName=Nome do painel de classificação |
| 53 | 53 | pleaseEnterAName=Insira um nome |
| 54 | 54 | pleaseEnterABoatClass=Insira uma classe de barcos |
| 55 | -discardRacesFromHowManyStartedRacesOn=Rejeitar uma ou mais corridas, começando com quantas corridas iniciadas existem |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Configure as rejeições. Insira no campo de seleção X o número de corridas pontuadas necessárias para aplicar a Xª rejeição. Máximo de rejeições: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Já existe um painel de classificação com este nome |
| 58 | 57 | discardThresholdsMustBeNumeric=Os limites do descarte devem ser numéricos |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Отменить связывание |
| 52 | 52 | leaderboardName=Название таблицы лидеров |
| 53 | 53 | pleaseEnterAName=Введите название |
| 54 | 54 | pleaseEnterABoatClass=Введите класс лодки |
| 55 | -discardRacesFromHowManyStartedRacesOn=Исключите одну или несколько гонок, исходя из числа стартов в гонках |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Настройте исключение. Введите в поле X, число оцениваемых гонок, необходимое для применения исключения X. Максимум исключений: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Таблица лидеров с таким названием уже существует |
| 58 | 57 | discardThresholdsMustBeNumeric=Пороги исключения должны быть числовыми |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_sl.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=Odstrani povezavo |
| 52 | 52 | leaderboardName=Ime lestvice vodilnih |
| 53 | 53 | pleaseEnterAName=Vnesite ime |
| 54 | 54 | pleaseEnterABoatClass=Vnesite kategorijo jadrnice |
| 55 | -discardRacesFromHowManyStartedRacesOn=Odstranite enega ali več plovov, ki se začne s številom razpoložljivih plovov |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=Konfigurirajte odstranitve. V okvirček vnesite X, število točkovanih plovov, potrebnih za uporabo X. odstranitve. Največje število odstranitev: {0}. |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=Lestvica vodilnih s tem imenom že obstaja |
| 58 | 57 | discardThresholdsMustBeNumeric=Mejne vrednosti odstranitve morajo biti številske |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
| ... | ... | @@ -52,7 +52,6 @@ unlink=取消链接 |
| 52 | 52 | leaderboardName=积分榜名称 |
| 53 | 53 | pleaseEnterAName=请输入名称 |
| 54 | 54 | pleaseEnterABoatClass=请输入船只级别 |
| 55 | -discardRacesFromHowManyStartedRacesOn=从已开始的比赛轮次数开始再放弃一个或多个比赛轮次 |
|
| 56 | 55 | applyDiscardsBasedOnNumberOfScoredRaces=配置放弃次数。在方框中输入 X,即申请第 X 次放弃所需的得分比赛次数。最大放弃次数:{0}。 |
| 57 | 56 | leaderboardWithThisNameAlreadyExists=具有此名称的积分榜已存在 |
| 58 | 57 | discardThresholdsMustBeNumeric=放弃临界值必须为数字 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java
| ... | ... | @@ -121,9 +121,9 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett |
| 121 | 121 | */ |
| 122 | 122 | private long effectiveStepSize = -1; |
| 123 | 123 | |
| 124 | - private final TimeRangeActionsExecutor<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, CompetitorDTO> timeRangeActionsExecutorForCompetitorRaceDataForPrimary; |
|
| 124 | + private final TimeRangeActionsExecutor<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, Pair<CompetitorDTO, DetailType>> timeRangeActionsExecutorForCompetitorRaceDataForPrimary; |
|
| 125 | 125 | |
| 126 | - private final TimeRangeActionsExecutor<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, CompetitorDTO> timeRangeActionsExecutorForCompetitorRaceDataForSecondary; |
|
| 126 | + private final TimeRangeActionsExecutor<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, Pair<CompetitorDTO, DetailType>> timeRangeActionsExecutorForCompetitorRaceDataForSecondary; |
|
| 127 | 127 | |
| 128 | 128 | AbstractCompetitorRaceChart(Component<?> parent, ComponentContext<?> context, SailingServiceAsync sailingService, |
| 129 | 129 | AsyncActionsExecutor asyncActionsExecutor, |
| ... | ... | @@ -281,13 +281,13 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett |
| 281 | 281 | |
| 282 | 282 | private void doLoadDataForCompetitorsAndDataType(final Date from, final Date to, final boolean append, |
| 283 | 283 | ArrayList<CompetitorDTO> competitorsToLoad, final DetailType selectedDataTypeToRetrieve, final TimingHolder tholder, |
| 284 | - TimeRangeActionsExecutor<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, CompetitorDTO> timeRangeActionExecutor) { |
|
| 284 | + TimeRangeActionsExecutor<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, Pair<CompetitorDTO, DetailType>> timeRangeActionExecutor) { |
|
| 285 | 285 | long effectiveStepSize = getEffectiveStepSize(from, to); |
| 286 | 286 | GetCompetitorsRaceDataAction getCompetitorsRaceDataAction = new GetCompetitorsRaceDataAction(sailingService, |
| 287 | 287 | selectedRaceIdentifier, competitorsToLoad, from, to, effectiveStepSize, selectedDataTypeToRetrieve, |
| 288 | 288 | leaderboardGroupName, leaderboardGroupId, leaderboardName); |
| 289 | - TimeRangeAsyncCallback<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, CompetitorDTO> dataLoadedCallback = |
|
| 290 | - new TimeRangeAsyncCallback<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, CompetitorDTO>() { |
|
| 289 | + TimeRangeAsyncCallback<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, Pair<CompetitorDTO, DetailType>> dataLoadedCallback = |
|
| 290 | + new TimeRangeAsyncCallback<CompetitorsRaceDataDTO, Triple<CompetitorRaceDataDTO, Date, Date>, Pair<CompetitorDTO, DetailType>>() { |
|
| 291 | 291 | @Override |
| 292 | 292 | public void onSuccess(final CompetitorsRaceDataDTO result) { |
| 293 | 293 | GWT.log("Received complete result for getCompetitorsRaceData request "+ |
| ... | ... | @@ -316,17 +316,17 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett |
| 316 | 316 | } |
| 317 | 317 | |
| 318 | 318 | @Override |
| 319 | - public Map<CompetitorDTO, Triple<CompetitorRaceDataDTO, Date, Date>> unzipResult(CompetitorsRaceDataDTO result) { |
|
| 320 | - final Map<CompetitorDTO, Triple<CompetitorRaceDataDTO, Date, Date>> unzipped = new HashMap<>(); |
|
| 319 | + public Map<Pair<CompetitorDTO, DetailType>, Triple<CompetitorRaceDataDTO, Date, Date>> unzipResult(CompetitorsRaceDataDTO result) { |
|
| 320 | + final Map<Pair<CompetitorDTO, DetailType>, Triple<CompetitorRaceDataDTO, Date, Date>> unzipped = new HashMap<>(); |
|
| 321 | 321 | for (final CompetitorRaceDataDTO competitorRaceData : result.getAllRaceData()) { |
| 322 | - unzipped.put(competitorRaceData.getCompetitor(), new Triple<>(competitorRaceData, |
|
| 323 | - result.getRequestedFromTime(), result.getRequestedToTime())); |
|
| 322 | + unzipped.put(new Pair<>(competitorRaceData.getCompetitor(), result.getDetailType()), |
|
| 323 | + new Triple<>(competitorRaceData, result.getRequestedFromTime(), result.getRequestedToTime())); |
|
| 324 | 324 | } |
| 325 | 325 | return unzipped; |
| 326 | 326 | } |
| 327 | 327 | |
| 328 | 328 | @Override |
| 329 | - public CompetitorsRaceDataDTO zipSubResults(Map<CompetitorDTO, Triple<CompetitorRaceDataDTO, Date, Date>> subResultMap) { |
|
| 329 | + public CompetitorsRaceDataDTO zipSubResults(Map<Pair<CompetitorDTO, DetailType>, Triple<CompetitorRaceDataDTO, Date, Date>> subResultMap) { |
|
| 330 | 330 | final DetailType detailType = subResultMap.values().stream().findFirst() |
| 331 | 331 | .map(competitorRaceDataWithDates -> competitorRaceDataWithDates.getA().getDetailType()).orElse(null); |
| 332 | 332 | final Date from = subResultMap.values().stream().findFirst() |
| ... | ... | @@ -334,8 +334,8 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett |
| 334 | 334 | final Date to = subResultMap.values().stream().findFirst() |
| 335 | 335 | .map(competitorRaceDataWithDates -> competitorRaceDataWithDates.getC()).orElse(null); |
| 336 | 336 | final Map<CompetitorDTO, CompetitorRaceDataDTO> raceDataPerCompetitor = new HashMap<>(); |
| 337 | - for (final Entry<CompetitorDTO, Triple<CompetitorRaceDataDTO, Date, Date>> e : subResultMap.entrySet()) { |
|
| 338 | - raceDataPerCompetitor.put(e.getKey(), e.getValue().getA()); |
|
| 337 | + for (final Entry<Pair<CompetitorDTO, DetailType>, Triple<CompetitorRaceDataDTO, Date, Date>> e : subResultMap.entrySet()) { |
|
| 338 | + raceDataPerCompetitor.put(e.getKey().getA(), e.getValue().getA()); |
|
| 339 | 339 | } |
| 340 | 340 | return new CompetitorsRaceDataDTO(detailType, from, to, raceDataPerCompetitor); |
| 341 | 341 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChartSettingsComponent.java
| ... | ... | @@ -4,6 +4,8 @@ import java.util.ArrayList; |
| 4 | 4 | import java.util.Collections; |
| 5 | 5 | import java.util.List; |
| 6 | 6 | |
| 7 | +import com.google.gwt.dom.client.NodeList; |
|
| 8 | +import com.google.gwt.dom.client.OptionElement; |
|
| 7 | 9 | import com.google.gwt.user.client.ui.FocusWidget; |
| 8 | 10 | import com.google.gwt.user.client.ui.Label; |
| 9 | 11 | import com.google.gwt.user.client.ui.ListBox; |
| ... | ... | @@ -14,6 +16,7 @@ import com.sap.sailing.gwt.ui.client.DetailTypeComparator; |
| 14 | 16 | import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; |
| 15 | 17 | import com.sap.sailing.gwt.ui.client.StringMessages; |
| 16 | 18 | import com.sap.sse.common.Util; |
| 19 | +import com.sap.sse.gwt.client.DOMUtils; |
|
| 17 | 20 | import com.sap.sse.gwt.client.dialog.DataEntryDialog; |
| 18 | 21 | import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; |
| 19 | 22 | |
| ... | ... | @@ -49,18 +52,26 @@ public class MultiCompetitorRaceChartSettingsComponent extends |
| 49 | 52 | Label chartSelectionLabel = new Label(stringMessages.chooseChart()); |
| 50 | 53 | mainPanel.add(chartSelectionLabel); |
| 51 | 54 | chartFirstTypeSelectionListBox = dialog.createListBox(/* isMultiSelect */false); |
| 55 | + final NodeList<OptionElement> firstTypeOptions = DOMUtils.getOptions(chartFirstTypeSelectionListBox); |
|
| 52 | 56 | chartSecondTypeSelectionListBox = dialog.createListBox(/* isMultiSelect */false); |
| 57 | + final NodeList<OptionElement> secondTypeOptions = DOMUtils.getOptions(chartSecondTypeSelectionListBox); |
|
| 53 | 58 | //add empty values, required, if a non available value is saved as default in the settings. Eg. rideheight, which is only valid for foiling races |
| 54 | 59 | chartSecondTypeSelectionListBox.addItem("--", "--"); |
| 55 | 60 | int i = 0; |
| 56 | - |
|
| 61 | + // sort detail types alphabetically first: |
|
| 57 | 62 | List<DetailType> sortedAvailableDetailTypes = new ArrayList<DetailType>(); |
| 58 | 63 | Util.addAll(availableDetailsTypes, sortedAvailableDetailTypes); |
| 59 | 64 | Collections.sort(sortedAvailableDetailTypes, new DetailTypeComparator()); |
| 60 | - |
|
| 65 | + // then add them to the list boxes, including their tool tips |
|
| 61 | 66 | for (DetailType detailType : sortedAvailableDetailTypes) { |
| 62 | - chartFirstTypeSelectionListBox.addItem(DetailTypeFormatter.format(detailType), detailType.name()); |
|
| 63 | - chartSecondTypeSelectionListBox.addItem(DetailTypeFormatter.format(detailType), detailType.name()); |
|
| 67 | + final String detailTypeString = DetailTypeFormatter.format(detailType); |
|
| 68 | + final String tooltip = DetailTypeFormatter.getTooltip(detailType); |
|
| 69 | + chartFirstTypeSelectionListBox.addItem(detailTypeString, detailType.name()); |
|
| 70 | + chartSecondTypeSelectionListBox.addItem(detailTypeString, detailType.name()); |
|
| 71 | + if (Util.hasLength(tooltip)) { |
|
| 72 | + firstTypeOptions.getItem(firstTypeOptions.getLength()-1).setTitle(tooltip); |
|
| 73 | + secondTypeOptions.getItem(secondTypeOptions.getLength()-1).setTitle(tooltip); |
|
| 74 | + } |
|
| 64 | 75 | if (detailType == initialFirstDetailType) { |
| 65 | 76 | chartFirstTypeSelectionListBox.setSelectedIndex(i); |
| 66 | 77 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java
| ... | ... | @@ -851,7 +851,6 @@ public class FixesAndTails { |
| 851 | 851 | protected Double getDetailValueAt(CompetitorDTO competitorDTO, int index) { |
| 852 | 852 | final Trigger<Integer> firstShownFixForCompetitor = firstShownFix.get(competitorDTO); |
| 853 | 853 | int indexOfFirstShownFix = (firstShownFixForCompetitor == null || firstShownFixForCompetitor.get() == null) ? -1 : firstShownFixForCompetitor.get(); |
| 854 | - |
|
| 855 | 854 | try { |
| 856 | 855 | return getFixes(competitorDTO).get(indexOfFirstShownFix + index).detailValue; |
| 857 | 856 | } catch (IndexOutOfBoundsException e) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
| ... | ... | @@ -19,6 +19,8 @@ import java.util.function.Consumer; |
| 19 | 19 | import com.google.gwt.core.client.GWT; |
| 20 | 20 | import com.google.gwt.core.client.Scheduler; |
| 21 | 21 | import com.google.gwt.core.client.Scheduler.ScheduledCommand; |
| 22 | +import com.google.gwt.dom.client.NodeList; |
|
| 23 | +import com.google.gwt.dom.client.OptionElement; |
|
| 22 | 24 | import com.google.gwt.dom.client.Style; |
| 23 | 25 | import com.google.gwt.dom.client.Style.Unit; |
| 24 | 26 | import com.google.gwt.event.dom.client.ChangeEvent; |
| ... | ... | @@ -161,6 +163,7 @@ import com.sap.sse.common.impl.DegreeBearingImpl; |
| 161 | 163 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 162 | 164 | import com.sap.sse.common.impl.RGBColor; |
| 163 | 165 | import com.sap.sse.common.impl.TimeRangeImpl; |
| 166 | +import com.sap.sse.gwt.client.DOMUtils; |
|
| 164 | 167 | import com.sap.sse.gwt.client.ErrorReporter; |
| 165 | 168 | import com.sap.sse.gwt.client.Notification; |
| 166 | 169 | import com.sap.sse.gwt.client.Notification.NotificationType; |
| ... | ... | @@ -403,7 +406,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme |
| 403 | 406 | private final TrueNorthIndicatorPanel trueNorthIndicatorPanel; |
| 404 | 407 | private final FlowPanel topLeftControlsWrapperPanel; |
| 405 | 408 | |
| 406 | - private final TimeRangeActionsExecutor<CompactBoatPositionsDTO, GPSFixDTOWithSpeedWindTackAndLegTypeIterable, String> timeRangeActionsExecutor; |
|
| 409 | + private final TimeRangeActionsExecutor<CompactBoatPositionsDTO, GPSFixDTOWithSpeedWindTackAndLegTypeIterable, Pair<String, DetailType>> timeRangeActionsExecutor; |
|
| 407 | 410 | private final AsyncActionsExecutor asyncActionsExecutor; |
| 408 | 411 | |
| 409 | 412 | /** |
| ... | ... | @@ -1504,7 +1507,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme |
| 1504 | 1507 | timeRangeActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, |
| 1505 | 1508 | fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall, /* extrapolate */ true, |
| 1506 | 1509 | detailType, leaderboardName, leaderboardGroupName, leaderboardGroupId), |
| 1507 | - new GetBoatPositionsCallback(new AsyncCallback<CompactBoatPositionsDTO>() { |
|
| 1510 | + new GetBoatPositionsCallback(detailType, new AsyncCallback<CompactBoatPositionsDTO>() { |
|
| 1508 | 1511 | @Override |
| 1509 | 1512 | public void onFailure(Throwable t) { |
| 1510 | 1513 | errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); |
| ... | ... | @@ -2864,12 +2867,17 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme |
| 2864 | 2867 | } |
| 2865 | 2868 | |
| 2866 | 2869 | private ListBox createDetailTypeDropdown(CompetitorDTO competitor) { |
| 2867 | - ListBox lb = new ListBox(); |
|
| 2870 | + final ListBox lb = new ListBox(); |
|
| 2871 | + final NodeList<OptionElement> options = DOMUtils.getOptions(lb); |
|
| 2868 | 2872 | lb.addItem(stringMessages.none(), "none"); |
| 2869 | 2873 | if (sortedAvailableDetailTypes != null) { |
| 2870 | 2874 | for (int i = 0; i < sortedAvailableDetailTypes.size(); i++) { |
| 2871 | - DetailType detail = sortedAvailableDetailTypes.get(i); |
|
| 2875 | + final DetailType detail = sortedAvailableDetailTypes.get(i); |
|
| 2872 | 2876 | lb.addItem(DetailTypeFormatter.format(detail), detail.name()); |
| 2877 | + final String tooltip = DetailTypeFormatter.getTooltip(detail); |
|
| 2878 | + if (Util.hasLength(tooltip)) { |
|
| 2879 | + options.getItem(options.getLength()-1).setTitle(tooltip); |
|
| 2880 | + } |
|
| 2873 | 2881 | if (detail == selectedDetailType) { |
| 2874 | 2882 | lb.setSelectedIndex(i + 1); |
| 2875 | 2883 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
| ... | ... | @@ -2456,16 +2456,12 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet |
| 2456 | 2456 | |
| 2457 | 2457 | @Override |
| 2458 | 2458 | public SwissTimingEventRecordDTO getRacesOfSwissTimingEvent(String eventJsonURL) |
| 2459 | - throws UnknownHostException, IOException, InterruptedException, ParseException { |
|
| 2459 | + throws UnknownHostException, IOException, InterruptedException, ParseException, URISyntaxException { |
|
| 2460 | 2460 | SwissTimingEventRecordDTO result = null; |
| 2461 | - List<SwissTimingRaceRecordDTO> swissTimingRaces = new ArrayList<SwissTimingRaceRecordDTO>(); |
|
| 2462 | - |
|
| 2463 | - // TODO: delete getSwissTimingAdapter().getSwissTimingRaceRecords() method |
|
| 2464 | - // TODO: delete SwissTimingDomainFactory.getRaceTypeFromRaceID(String raceID) |
|
| 2465 | - URL url = new URL(eventJsonURL); |
|
| 2466 | - URLConnection eventResultConn = HttpUrlConnectionHelper.redirectConnection(url); |
|
| 2467 | - Manage2SailEventResultsParserImpl parser = new Manage2SailEventResultsParserImpl(); |
|
| 2468 | - EventResultDescriptor eventResult = parser.getEventResult((InputStream) eventResultConn.getContent()); |
|
| 2461 | + final List<SwissTimingRaceRecordDTO> swissTimingRaces = new ArrayList<>(); |
|
| 2462 | + final URL url = new URL(eventJsonURL); |
|
| 2463 | + final Manage2SailEventResultsParserImpl parser = new Manage2SailEventResultsParserImpl(); |
|
| 2464 | + final EventResultDescriptor eventResult = parser.getEventResult(url); |
|
| 2469 | 2465 | if (eventResult != null) { |
| 2470 | 2466 | for (RegattaResultDescriptor regattaResult : eventResult.getRegattaResults()) { |
| 2471 | 2467 | for (RaceResultDescriptor race : regattaResult.getRaceResults()) { |
| ... | ... | @@ -4077,12 +4073,13 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet |
| 4077 | 4073 | } |
| 4078 | 4074 | |
| 4079 | 4075 | @Override |
| 4080 | - public Iterable<RegattaDTO> getRegattas(String manage2SailJsonUrl) { |
|
| 4081 | - StructureImporter structureImporter = new StructureImporter(new SetRacenumberFromSeries(), baseDomainFactory); |
|
| 4082 | - Iterable<RegattaJSON> parsedEvent = structureImporter.parseEvent(manage2SailJsonUrl); |
|
| 4083 | - List<RegattaDTO> regattaDTOs = new ArrayList<RegattaDTO>(); |
|
| 4084 | - Iterable<Regatta> regattas = structureImporter.getRegattas(parsedEvent); |
|
| 4085 | - for (Regatta regatta : regattas) { |
|
| 4076 | + public Iterable<RegattaDTO> getManage2SailRegattas(String manage2SailJsonUrl) throws MalformedURLException, URISyntaxException { |
|
| 4077 | + final StructureImporter structureImporter = new StructureImporter(new SetRacenumberFromSeries(), baseDomainFactory); |
|
| 4078 | + final String manage2SailJsonUrlWithAccessToken = com.sap.sailing.manage2sail.Activator.getInstance().addAccessTokenToManage2SailUrl(new URL(manage2SailJsonUrl)).toString(); |
|
| 4079 | + final Iterable<RegattaJSON> parsedEvent = structureImporter.parseEvent(manage2SailJsonUrlWithAccessToken); |
|
| 4080 | + final List<RegattaDTO> regattaDTOs = new ArrayList<RegattaDTO>(); |
|
| 4081 | + final Iterable<Regatta> regattas = structureImporter.getRegattas(parsedEvent); |
|
| 4082 | + for (final Regatta regatta : regattas) { |
|
| 4086 | 4083 | regattaDTOs.add(convertToRegattaDTO(regatta)); |
| 4087 | 4084 | } |
| 4088 | 4085 | return regattaDTOs; |
java/com.sap.sailing.kiworesultimport/src/com/sap/sailing/kiworesultimport/impl/ScoreCorrectionProviderImpl.java
| ... | ... | @@ -3,6 +3,7 @@ package com.sap.sailing.kiworesultimport.impl; |
| 3 | 3 | import java.io.File; |
| 4 | 4 | import java.io.IOException; |
| 5 | 5 | import java.io.InputStream; |
| 6 | +import java.net.URISyntaxException; |
|
| 6 | 7 | import java.util.ArrayList; |
| 7 | 8 | import java.util.HashMap; |
| 8 | 9 | import java.util.HashSet; |
| ... | ... | @@ -40,7 +41,7 @@ public class ScoreCorrectionProviderImpl extends AbstractFileBasedScoreCorrectio |
| 40 | 41 | |
| 41 | 42 | @Override |
| 42 | 43 | public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() |
| 43 | - throws IOException, SAXException, ParserConfigurationException { |
|
| 44 | + throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 44 | 45 | Map<String, Set<Util.Pair<String, TimePoint>>> result = new HashMap<String, Set<Util.Pair<String, TimePoint>>>(); |
| 45 | 46 | for (RegattaSummary regattaSummary : getAllRegattaSummaries()) { |
| 46 | 47 | String eventName = regattaSummary.getEventName(); |
| ... | ... | @@ -71,7 +72,7 @@ public class ScoreCorrectionProviderImpl extends AbstractFileBasedScoreCorrectio |
| 71 | 72 | return result; |
| 72 | 73 | } |
| 73 | 74 | |
| 74 | - private Iterable<RegattaSummary> getAllRegattaSummaries() throws IOException, SAXException, ParserConfigurationException { |
|
| 75 | + private Iterable<RegattaSummary> getAllRegattaSummaries() throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 75 | 76 | final List<RegattaSummary> result = new ArrayList<RegattaSummary>(); |
| 76 | 77 | for (ResultDocumentDescriptor resultDocDescr : getResultDocumentProvider().getResultDocumentDescriptors()) { |
| 77 | 78 | if (resultDocDescr.getDocumentName().toLowerCase().endsWith(".zip")) { |
| ... | ... | @@ -88,7 +89,7 @@ public class ScoreCorrectionProviderImpl extends AbstractFileBasedScoreCorrectio |
| 88 | 89 | } |
| 89 | 90 | @Override |
| 90 | 91 | public RegattaScoreCorrections getScoreCorrections(String eventName, String boatClassName, |
| 91 | - TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException { |
|
| 92 | + TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 92 | 93 | for (RegattaSummary regattaSummary : getAllRegattaSummaries()) { |
| 93 | 94 | if (regattaSummary.getEventName().equals(eventName) && regattaSummary.getBoatClassName().equals(boatClassName) && |
| 94 | 95 | regattaSummary.getTimePointPublished().equals(timePointPublished)) { |
java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java
| ... | ... | @@ -8,6 +8,7 @@ import static org.mockito.Mockito.when; |
| 8 | 8 | |
| 9 | 9 | import java.io.FileNotFoundException; |
| 10 | 10 | import java.io.IOException; |
| 11 | +import java.net.URISyntaxException; |
|
| 11 | 12 | import java.net.URL; |
| 12 | 13 | import java.util.Arrays; |
| 13 | 14 | |
| ... | ... | @@ -38,7 +39,7 @@ import com.sap.sse.common.Util; |
| 38 | 39 | */ |
| 39 | 40 | public class CompetitorImportTest extends AbstractEventResultJsonServiceTest { |
| 40 | 41 | @Test |
| 41 | - public void simpleCompetitorImportTest() throws FileNotFoundException, IOException, JAXBException { |
|
| 42 | + public void simpleCompetitorImportTest() throws FileNotFoundException, IOException, JAXBException, URISyntaxException { |
|
| 42 | 43 | ResultUrlRegistry resultUrlRegistry = mock(ResultUrlRegistry.class); |
| 43 | 44 | when(resultUrlRegistry.getReadableResultUrls(AbstractManage2SailProvider.NAME)).thenReturn(Arrays.asList(getClass().getClassLoader().getResource(EVENT_RESULTS_JSON+".txt"))); |
| 44 | 45 | final Manage2SailCompetitorProvider competitorImporter = new Manage2SailCompetitorProvider(ParserFactory.INSTANCE, resultUrlRegistry); |
| ... | ... | @@ -53,7 +54,7 @@ public class CompetitorImportTest extends AbstractEventResultJsonServiceTest { |
| 53 | 54 | } |
| 54 | 55 | |
| 55 | 56 | @Test |
| 56 | - public void simpleCompetitorImportTestNoResultsYet() throws FileNotFoundException, IOException, JAXBException { |
|
| 57 | + public void simpleCompetitorImportTestNoResultsYet() throws FileNotFoundException, IOException, JAXBException, URISyntaxException { |
|
| 57 | 58 | ResultUrlRegistry resultUrlRegistry = mock(ResultUrlRegistry.class); |
| 58 | 59 | when(resultUrlRegistry.getReadableResultUrls(AbstractManage2SailProvider.NAME)).thenReturn(Arrays.asList(getClass().getClassLoader().getResource("VSaW_420_Test.json.txt"))); |
| 59 | 60 | final CompetitorProvider competitorImporter = new Manage2SailCompetitorProvider(ParserFactory.INSTANCE, resultUrlRegistry) { |
| ... | ... | @@ -77,7 +78,7 @@ public class CompetitorImportTest extends AbstractEventResultJsonServiceTest { |
| 77 | 78 | } |
| 78 | 79 | |
| 79 | 80 | @Test |
| 80 | - public void simpleCompetitorImportTestNoResultsYetAndDivisionEmpty() throws FileNotFoundException, IOException, JAXBException { |
|
| 81 | + public void simpleCompetitorImportTestNoResultsYetAndDivisionEmpty() throws FileNotFoundException, IOException, JAXBException, URISyntaxException { |
|
| 81 | 82 | ResultUrlRegistry resultUrlRegistry = mock(ResultUrlRegistry.class); |
| 82 | 83 | when(resultUrlRegistry.getReadableResultUrls(AbstractManage2SailProvider.NAME)).thenReturn(Arrays.asList(getClass().getClassLoader().getResource("VSaW_420_Test_EmptyDivision.json.txt"))); |
| 83 | 84 | final CompetitorProvider competitorImporter = new Manage2SailCompetitorProvider(ParserFactory.INSTANCE, resultUrlRegistry) { |
java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/ParserTest.java
| ... | ... | @@ -10,6 +10,7 @@ import java.io.FileInputStream; |
| 10 | 10 | import java.io.FileNotFoundException; |
| 11 | 11 | import java.io.IOException; |
| 12 | 12 | import java.io.InputStream; |
| 13 | +import java.net.URISyntaxException; |
|
| 13 | 14 | import java.util.ArrayList; |
| 14 | 15 | import java.util.Arrays; |
| 15 | 16 | import java.util.Date; |
| ... | ... | @@ -103,7 +104,7 @@ public class ParserTest { |
| 103 | 104 | |
| 104 | 105 | @Test |
| 105 | 106 | public void testScoreCorrectionProviderFeedingAndHasResults() throws IOException, SAXException, |
| 106 | - ParserConfigurationException, JAXBException { |
|
| 107 | + ParserConfigurationException, JAXBException, URISyntaxException { |
|
| 107 | 108 | ResultUrlRegistry resultUrlRegistry = new ResultUrlRegistryImpl(mock(MongoObjectFactory.class), |
| 108 | 109 | mock(DomainObjectFactory.class)); |
| 109 | 110 | ScoreCorrectionProviderImpl scoreCorrectionProvider = new ScoreCorrectionProviderImpl( |
java/com.sap.sailing.manage2sail.resultimport/src/com/sap/sailing/manage2sail/resultimport/AbstractManage2SailProvider.java
| ... | ... | @@ -14,13 +14,14 @@ public abstract class AbstractManage2SailProvider extends AbstractResultUrlProvi |
| 14 | 14 | public static final String NAME = "Manage2Sail XRR Result Importer"; |
| 15 | 15 | |
| 16 | 16 | protected static final String EVENT_ID_REGEX = "^[\\da-f]{8}(-[\\da-f]{4}){3}-[\\da-f]{12}$"; |
| 17 | - protected static final String EVENT_ID_TEMPLATE = "http://manage2sail.com/api/public/links/event/%s?accesstoken=bDAv8CwsTM94ujZ&mediaType=json"; |
|
| 17 | + private static String EVENT_ID_TEMPLATE; |
|
| 18 | 18 | |
| 19 | 19 | private final ParserFactory parserFactory; |
| 20 | 20 | |
| 21 | 21 | protected AbstractManage2SailProvider(ParserFactory parserFactory, ResultUrlRegistry resultUrlRegistry) { |
| 22 | 22 | super(resultUrlRegistry); |
| 23 | 23 | this.parserFactory = parserFactory; |
| 24 | + EVENT_ID_TEMPLATE = "https://"+com.sap.sailing.manage2sail.Activator.getInstance().getManage2SailHostname()+"/api/public/links/event/%s?mediaType=json"; |
|
| 24 | 25 | } |
| 25 | 26 | |
| 26 | 27 | protected ParserFactory getParserFactory() { |
| ... | ... | @@ -31,13 +32,17 @@ public abstract class AbstractManage2SailProvider extends AbstractResultUrlProvi |
| 31 | 32 | public URL resolveUrl(String url) throws MalformedURLException { |
| 32 | 33 | String completedUrl = url; |
| 33 | 34 | if (url.matches(EVENT_ID_REGEX)) { |
| 34 | - completedUrl = String.format(EVENT_ID_TEMPLATE, url); |
|
| 35 | + completedUrl = String.format(getEventIdTemplate(), url); |
|
| 35 | 36 | } |
| 36 | - return new URL(completedUrl); //TODO Find a better way to check if a URL is valid |
|
| 37 | + return new URL(completedUrl); // TODO Find a better way to check if a URL is valid |
|
| 37 | 38 | } |
| 38 | 39 | |
| 39 | 40 | @Override |
| 40 | 41 | public String getOptionalSampleURL() { |
| 41 | - return "http://manage2sail.com/api/public/links/event/d30883d3-2876-4d7e-af49-891af6cbae1b?accesstoken=bDAv8CwsTM94ujZ&mediaType=json"; |
|
| 42 | + return String.format(getEventIdTemplate(), "d30883d3-2876-4d7e-af49-891af6cbae1b"); |
|
| 43 | + } |
|
| 44 | + |
|
| 45 | + protected static String getEventIdTemplate() { |
|
| 46 | + return EVENT_ID_TEMPLATE; |
|
| 42 | 47 | } |
| 43 | 48 | } |
java/com.sap.sailing.manage2sail.resultimport/src/com/sap/sailing/manage2sail/resultimport/AbstractManage2SailResultDocumentProvider.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.manage2sail.resultimport; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | +import java.net.URISyntaxException; |
|
| 5 | 6 | import java.net.URL; |
| 6 | 7 | import java.net.URLConnection; |
| 7 | 8 | import java.util.ArrayList; |
| ... | ... | @@ -34,12 +35,11 @@ public abstract class AbstractManage2SailResultDocumentProvider implements Resul |
| 34 | 35 | } |
| 35 | 36 | |
| 36 | 37 | @Override |
| 37 | - public Iterable<ResultDocumentDescriptor> getResultDocumentDescriptors() throws IOException { |
|
| 38 | + public Iterable<ResultDocumentDescriptor> getResultDocumentDescriptors() throws IOException, URISyntaxException { |
|
| 38 | 39 | List<ResultDocumentDescriptor> result = new ArrayList<>(); |
| 39 | 40 | Manage2SailEventResultsParserImpl parser = new Manage2SailEventResultsParserImpl(); |
| 40 | 41 | for (URL url : resultUrlProvider.getReadableUrls()) { |
| 41 | - URLConnection eventResultConn = HttpUrlConnectionHelper.redirectConnection(url); |
|
| 42 | - EventResultDescriptor eventResult = parser.getEventResult((InputStream) eventResultConn.getContent()); |
|
| 42 | + EventResultDescriptor eventResult = parser.getEventResult(url); |
|
| 43 | 43 | addResultsForEvent(result, eventResult); |
| 44 | 44 | } |
| 45 | 45 | return result; |
java/com.sap.sailing.manage2sail.resultimport/src/com/sap/sailing/manage2sail/resultimport/CompetitorDocumentProvider.java
| ... | ... | @@ -1,11 +1,9 @@ |
| 1 | 1 | package com.sap.sailing.manage2sail.resultimport; |
| 2 | 2 | |
| 3 | -import java.io.IOException; |
|
| 4 | 3 | import java.net.URL; |
| 5 | 4 | |
| 6 | 5 | import com.sap.sailing.domain.resultimport.ResultUrlProvider; |
| 7 | 6 | import com.sap.sailing.manage2sail.RegattaResultDescriptor; |
| 8 | -import com.sap.sailing.resultimport.ResultDocumentDescriptor; |
|
| 9 | 7 | |
| 10 | 8 | public class CompetitorDocumentProvider extends AbstractManage2SailResultDocumentProvider { |
| 11 | 9 | public CompetitorDocumentProvider(ResultUrlProvider resultUrlProvider) { |
| ... | ... | @@ -13,11 +11,6 @@ public class CompetitorDocumentProvider extends AbstractManage2SailResultDocumen |
| 13 | 11 | } |
| 14 | 12 | |
| 15 | 13 | @Override |
| 16 | - public Iterable<ResultDocumentDescriptor> getResultDocumentDescriptors() throws IOException { |
|
| 17 | - return super.getResultDocumentDescriptors(); |
|
| 18 | - } |
|
| 19 | - |
|
| 20 | - @Override |
|
| 21 | 14 | protected URL getDocumentUrlForRegatta(RegattaResultDescriptor regattaResult) { |
| 22 | 15 | return regattaResult.getXrrEntriesUrl(); |
| 23 | 16 | } |
java/com.sap.sailing.manage2sail.resultimport/src/com/sap/sailing/manage2sail/resultimport/Manage2SailCompetitorProvider.java
| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | package com.sap.sailing.manage2sail.resultimport; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | +import java.net.URISyntaxException; |
|
| 4 | 5 | import java.util.Locale; |
| 5 | 6 | import java.util.Map; |
| 6 | 7 | import java.util.Set; |
| ... | ... | @@ -31,7 +32,7 @@ public class Manage2SailCompetitorProvider extends AbstractManage2SailProvider i |
| 31 | 32 | } |
| 32 | 33 | |
| 33 | 34 | @Override |
| 34 | - public Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException { |
|
| 35 | + public Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException, URISyntaxException { |
|
| 35 | 36 | return competitorResolver.getHasCompetitorsForRegattasInEvent(); |
| 36 | 37 | } |
| 37 | 38 | |
| ... | ... | @@ -41,7 +42,7 @@ public class Manage2SailCompetitorProvider extends AbstractManage2SailProvider i |
| 41 | 42 | } |
| 42 | 43 | |
| 43 | 44 | @Override |
| 44 | - public Iterable<CompetitorDescriptor> getCompetitorDescriptors(String eventName, String regattaName) throws JAXBException, IOException { |
|
| 45 | + public Iterable<CompetitorDescriptor> getCompetitorDescriptors(String eventName, String regattaName) throws JAXBException, IOException, URISyntaxException { |
|
| 45 | 46 | return competitorResolver.getCompetitorDescriptors(eventName, regattaName); |
| 46 | 47 | } |
| 47 | 48 |
java/com.sap.sailing.manage2sail.resultimport/src/com/sap/sailing/manage2sail/resultimport/ScoreCorrectionProviderImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.manage2sail.resultimport; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | +import java.net.URISyntaxException; |
|
| 5 | 6 | import java.util.HashMap; |
| 6 | 7 | import java.util.HashSet; |
| 7 | 8 | import java.util.Map; |
| ... | ... | @@ -47,7 +48,7 @@ public class ScoreCorrectionProviderImpl extends AbstractManage2SailProvider imp |
| 47 | 48 | } |
| 48 | 49 | |
| 49 | 50 | @Override |
| 50 | - public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() { |
|
| 51 | + public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() throws URISyntaxException { |
|
| 51 | 52 | Map<String, Set<Util.Pair<String, TimePoint>>> result = new HashMap<String, Set<Util.Pair<String,TimePoint>>>(); |
| 52 | 53 | try { |
| 53 | 54 | for (ResultDocumentDescriptor resultDocDescr : documentProvider.getResultDocumentDescriptors()) { |
| ... | ... | @@ -73,7 +74,7 @@ public class ScoreCorrectionProviderImpl extends AbstractManage2SailProvider imp |
| 73 | 74 | |
| 74 | 75 | @Override |
| 75 | 76 | public RegattaScoreCorrections getScoreCorrections(String eventName, String boatClassName, |
| 76 | - TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException { |
|
| 77 | + TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 77 | 78 | final Parser parser = resolveParser(eventName, boatClassName, timePointPublished); |
| 78 | 79 | try { |
| 79 | 80 | final RegattaResults regattaResults = parser.parse(); |
| ... | ... | @@ -94,7 +95,7 @@ public class ScoreCorrectionProviderImpl extends AbstractManage2SailProvider imp |
| 94 | 95 | /* boatClassNameFilter */ Optional.empty()); |
| 95 | 96 | } |
| 96 | 97 | |
| 97 | - private Parser resolveParser(String eventName, String boatClassName, TimePoint timePointPublished) throws IOException { |
|
| 98 | + private Parser resolveParser(String eventName, String boatClassName, TimePoint timePointPublished) throws IOException, URISyntaxException { |
|
| 98 | 99 | Parser result = null; |
| 99 | 100 | for (ResultDocumentDescriptor resultDocDescr : documentProvider.getResultDocumentDescriptors()) { |
| 100 | 101 | String boatClassAndGenderType = resultDocDescr.getBoatClass(); |
java/com.sap.sailing.manage2sail/META-INF/MANIFEST.MF
| ... | ... | @@ -6,9 +6,13 @@ Bundle-Version: 1.0.0.qualifier |
| 6 | 6 | Bundle-Vendor: SAP |
| 7 | 7 | Bundle-RequiredExecutionEnvironment: JavaSE-1.8 |
| 8 | 8 | Bundle-ActivationPolicy: lazy |
| 9 | +Bundle-Activator: com.sap.sailing.manage2sail.Activator |
|
| 9 | 10 | Require-Bundle: org.json.simple;bundle-version="1.1.0", |
| 10 | 11 | com.sap.sailing.domain.common, |
| 11 | 12 | com.sap.sailing.domain, |
| 12 | 13 | com.sap.sse |
| 13 | 14 | Export-Package: com.sap.sailing.manage2sail |
| 15 | +Import-Package: org.apache.http;version="4.4.9", |
|
| 16 | + org.apache.http.client.utils;version="4.5.5", |
|
| 17 | + org.osgi.framework |
|
| 14 | 18 | Automatic-Module-Name: com.sap.sailing.manage2sail |
java/com.sap.sailing.manage2sail/src/com/sap/sailing/manage2sail/Activator.java
| ... | ... | @@ -0,0 +1,59 @@ |
| 1 | +package com.sap.sailing.manage2sail; |
|
| 2 | + |
|
| 3 | +import java.net.MalformedURLException; |
|
| 4 | +import java.net.URISyntaxException; |
|
| 5 | +import java.net.URL; |
|
| 6 | +import org.apache.http.client.utils.URIBuilder; |
|
| 7 | +import org.osgi.framework.BundleActivator; |
|
| 8 | +import org.osgi.framework.BundleContext; |
|
| 9 | + |
|
| 10 | +public class Activator implements BundleActivator { |
|
| 11 | + private static final String MANAGE2SAIL_HOSTNAME = "www.manage2sail.com"; |
|
| 12 | + private static final String ACCESS_TOKEN_PROPERTY_NAME = "manage2sail.accesstoken"; |
|
| 13 | + private static final String ACCESS_TOKEN_URL_QUERY_PARAMETER_NAME = "accesstoken"; |
|
| 14 | + private static Activator INSTANCE; |
|
| 15 | + private String accessToken; |
|
| 16 | + |
|
| 17 | + @Override |
|
| 18 | + public void start(BundleContext context) throws Exception { |
|
| 19 | + INSTANCE = this; |
|
| 20 | + accessToken = System.getProperty(ACCESS_TOKEN_PROPERTY_NAME); |
|
| 21 | + } |
|
| 22 | + |
|
| 23 | + @Override |
|
| 24 | + public void stop(BundleContext context) throws Exception { |
|
| 25 | + } |
|
| 26 | + |
|
| 27 | + public static Activator getInstance() { |
|
| 28 | + if (INSTANCE == null) { // test scenario maybe |
|
| 29 | + INSTANCE = new Activator(); |
|
| 30 | + } |
|
| 31 | + return INSTANCE; |
|
| 32 | + } |
|
| 33 | + |
|
| 34 | + private String getAccessToken() { |
|
| 35 | + return accessToken; |
|
| 36 | + } |
|
| 37 | + |
|
| 38 | + /** |
|
| 39 | + * If and only if the {@link URL#getHost() host} part of the URL equals the value of the {@link #getManage2SailHostname()} and |
|
| 40 | + * there is not yet a query parameter named {@code accesstoken} in the {@code url}, |
|
| 41 | + * a new URL with an "accesstoken=${accessToken}" parameter appended to the {@link URL#getQuery() query} part of the {@code url} |
|
| 42 | + * will be returned. Otherwise, the {@code url} will be returned unchanged. |
|
| 43 | + */ |
|
| 44 | + public URL addAccessTokenToManage2SailUrl(URL url) throws URISyntaxException, MalformedURLException { |
|
| 45 | + final URL result; |
|
| 46 | + final URIBuilder uriBuilder = new URIBuilder(url.toURI()); |
|
| 47 | + final boolean containsAccessTokenQueryParam = uriBuilder.getQueryParams().stream().anyMatch(nameValuePair -> nameValuePair.getName().equals(ACCESS_TOKEN_URL_QUERY_PARAMETER_NAME)); |
|
| 48 | + if (url.getHost().equals(getManage2SailHostname()) && !containsAccessTokenQueryParam) { |
|
| 49 | + result = uriBuilder.addParameter(ACCESS_TOKEN_URL_QUERY_PARAMETER_NAME, getAccessToken()).build().toURL(); |
|
| 50 | + } else { |
|
| 51 | + result = url; |
|
| 52 | + } |
|
| 53 | + return result; |
|
| 54 | + } |
|
| 55 | + |
|
| 56 | + public String getManage2SailHostname() { |
|
| 57 | + return MANAGE2SAIL_HOSTNAME; |
|
| 58 | + } |
|
| 59 | +} |
java/com.sap.sailing.manage2sail/src/com/sap/sailing/manage2sail/Manage2SailEventResultsParser.java
| ... | ... | @@ -4,5 +4,5 @@ import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | 5 | |
| 6 | 6 | public interface Manage2SailEventResultsParser { |
| 7 | - public EventResultDescriptor getEventResult(InputStream is) throws IOException; |
|
| 7 | + EventResultDescriptor getEventResult(InputStream is) throws IOException; |
|
| 8 | 8 | } |
java/com.sap.sailing.manage2sail/src/com/sap/sailing/manage2sail/Manage2SailEventResultsParserImpl.java
| ... | ... | @@ -4,6 +4,7 @@ import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | 5 | import java.io.InputStreamReader; |
| 6 | 6 | import java.net.MalformedURLException; |
| 7 | +import java.net.URISyntaxException; |
|
| 7 | 8 | import java.net.URL; |
| 8 | 9 | import java.util.Date; |
| 9 | 10 | |
| ... | ... | @@ -15,11 +16,12 @@ import org.json.simple.parser.ParseException; |
| 15 | 16 | import com.sap.sailing.domain.common.CompetitorGenderType; |
| 16 | 17 | import com.sap.sse.InvalidDateException; |
| 17 | 18 | import com.sap.sse.util.DateParser; |
| 19 | +import com.sap.sse.util.HttpUrlConnectionHelper; |
|
| 18 | 20 | |
| 19 | 21 | /** |
| 20 | 22 | * URLs should be of the form |
| 21 | 23 | * <pre> |
| 22 | - * http://manage2sail.com/api/public/links/event/d30883d3-2876-4d7e-af49-891af6cbae1b?accesstoken=bDAv8CwsTM94ujZ&mediaType=json |
|
| 24 | + * https://www.manage2sail.com/api/public/links/event/d30883d3-2876-4d7e-af49-891af6cbae1b?accesstoken=bDAv8CwsTM94ujZ&mediaType=json |
|
| 23 | 25 | * </pre> |
| 24 | 26 | * where the UUID following the <code>event</code> path element represents the event ID. Events can be |
| 25 | 27 | * discovered by the manage2sail.com website. |
| ... | ... | @@ -28,7 +30,11 @@ import com.sap.sse.util.DateParser; |
| 28 | 30 | * |
| 29 | 31 | */ |
| 30 | 32 | public class Manage2SailEventResultsParserImpl implements Manage2SailEventResultsParser { |
| 31 | - |
|
| 33 | + public EventResultDescriptor getEventResult(URL eventJsonUrl) throws MalformedURLException, IOException, URISyntaxException { |
|
| 34 | + return getEventResult((InputStream) HttpUrlConnectionHelper |
|
| 35 | + .redirectConnection(Activator.getInstance().addAccessTokenToManage2SailUrl(eventJsonUrl)).getContent()); |
|
| 36 | + } |
|
| 37 | + |
|
| 32 | 38 | /** |
| 33 | 39 | * @param is closed before the method returns, also in case of exception |
| 34 | 40 | */ |
java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages_de.properties
| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | PolarChain1=Polare |
| 2 | 2 | PolarChain2=Backend-Polare |
| 3 | -LegIndex=Schenkel Index |
|
| 3 | +LegIndex=Bahnschenkel Index |
|
| 4 | 4 | PolarData=Polardaten |
| 5 | 5 | Polars=Polare |
| 6 | 6 | GPSFix=GPS Fix |
java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/ResultDocumentProvider.java
| ... | ... | @@ -1,7 +1,8 @@ |
| 1 | 1 | package com.sap.sailing.resultimport; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | +import java.net.URISyntaxException; |
|
| 4 | 5 | |
| 5 | 6 | public interface ResultDocumentProvider { |
| 6 | - Iterable<ResultDocumentDescriptor> getResultDocumentDescriptors() throws IOException; |
|
| 7 | + Iterable<ResultDocumentDescriptor> getResultDocumentDescriptors() throws IOException, URISyntaxException; |
|
| 7 | 8 | } |
java/com.sap.sailing.sailti.resultimport.test/src/com/sap/sailing/sailti/resultimport/test/TrofeoSofia_ParserTest.java
| ... | ... | @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull; |
| 5 | 5 | import static org.mockito.Mockito.mock; |
| 6 | 6 | |
| 7 | 7 | import java.io.IOException; |
| 8 | +import java.net.URISyntaxException; |
|
| 8 | 9 | import java.util.Map; |
| 9 | 10 | import java.util.Set; |
| 10 | 11 | |
| ... | ... | @@ -41,7 +42,7 @@ public class TrofeoSofia_ParserTest extends AbstractTrofeoSofiaTest { |
| 41 | 42 | |
| 42 | 43 | @Test |
| 43 | 44 | public void testScoreCorrectionProviderFeedingAndHasResults() throws IOException, SAXException, |
| 44 | - ParserConfigurationException, JAXBException { |
|
| 45 | + ParserConfigurationException, JAXBException, URISyntaxException { |
|
| 45 | 46 | ResultUrlRegistry resultUrlRegistry = new ResultUrlRegistryImpl(mock(MongoObjectFactory.class), |
| 46 | 47 | mock(DomainObjectFactory.class)); |
| 47 | 48 | ScoreCorrectionProviderImpl scoreCorrectionProvider = new ScoreCorrectionProviderImpl( |
java/com.sap.sailing.sailti.resultimport/src/com/sap/sailing/sailti/resultimport/SailtiEventResultsParserImpl.java
| ... | ... | @@ -25,13 +25,6 @@ import com.sap.sse.util.HttpUrlConnectionHelper; |
| 25 | 25 | * for that class can parse the document into an {@link EventResultDescriptor} from which the regatta results can be obtained |
| 26 | 26 | * using {@link EventResultDescriptor#getRegattaResults()}.<p> |
| 27 | 27 | * |
| 28 | - * TODO bug5693 Sailti URL format...: URLs should be of the form |
|
| 29 | - * <pre> |
|
| 30 | - * http://manage2sail.com/api/public/links/event/d30883d3-2876-4d7e-af49-891af6cbae1b?accesstoken=bDAv8CwsTM94ujZ&mediaType=json |
|
| 31 | - * </pre> |
|
| 32 | - * where the UUID following the <code>event</code> path element represents the event ID. Events can be |
|
| 33 | - * discovered by the manage2sail.com website. |
|
| 34 | - * |
|
| 35 | 28 | * @author Axel Uhl (D043530) |
| 36 | 29 | * |
| 37 | 30 | */ |
java/com.sap.sailing.sailti.resultimport/src/com/sap/sailing/sailti/resultimport/ScoreCorrectionProviderImpl.java
| ... | ... | @@ -3,6 +3,7 @@ package com.sap.sailing.sailti.resultimport; |
| 3 | 3 | import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | 5 | import java.net.MalformedURLException; |
| 6 | +import java.net.URISyntaxException; |
|
| 6 | 7 | import java.net.URL; |
| 7 | 8 | import java.util.HashMap; |
| 8 | 9 | import java.util.HashSet; |
| ... | ... | @@ -56,7 +57,7 @@ public class ScoreCorrectionProviderImpl extends AbstractResultUrlProvider imple |
| 56 | 57 | } |
| 57 | 58 | |
| 58 | 59 | @Override |
| 59 | - public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() { |
|
| 60 | + public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() throws URISyntaxException { |
|
| 60 | 61 | Map<String, Set<Util.Pair<String, TimePoint>>> result = new HashMap<String, Set<Util.Pair<String,TimePoint>>>(); |
| 61 | 62 | try { |
| 62 | 63 | for (ResultDocumentDescriptor resultDocDescr : documentProvider.getResultDocumentDescriptors()) { |
| ... | ... | @@ -82,7 +83,7 @@ public class ScoreCorrectionProviderImpl extends AbstractResultUrlProvider imple |
| 82 | 83 | |
| 83 | 84 | @Override |
| 84 | 85 | public RegattaScoreCorrections getScoreCorrections(String eventName, String boatClassName, |
| 85 | - TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException { |
|
| 86 | + TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 86 | 87 | Parser parser = resolveParser(eventName, boatClassName); |
| 87 | 88 | try { |
| 88 | 89 | RegattaResults regattaResults = parser.parse(); |
| ... | ... | @@ -103,7 +104,7 @@ public class ScoreCorrectionProviderImpl extends AbstractResultUrlProvider imple |
| 103 | 104 | /* boatClassNameFilter */ Optional.empty()); |
| 104 | 105 | } |
| 105 | 106 | |
| 106 | - private Parser resolveParser(String eventName, String boatClassName) throws IOException { |
|
| 107 | + private Parser resolveParser(String eventName, String boatClassName) throws IOException, URISyntaxException { |
|
| 107 | 108 | Parser result = null; |
| 108 | 109 | for (ResultDocumentDescriptor resultDocDescr : documentProvider.getResultDocumentDescriptors()) { |
| 109 | 110 | if (eventName.equals(resultDocDescr.getEventName()) && boatClassName.equals(resultDocDescr.getBoatClass())) { |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/PageObject.java
| ... | ... | @@ -383,6 +383,7 @@ public class PageObject { |
| 383 | 383 | /** |
| 384 | 384 | * <p>Finds and returns the first element with the specified selenium id in the given search context. If multiple |
| 385 | 385 | * elements exists, the element closest to the context is returned.</p> |
| 386 | + * <p>To be more stable this search is based on a web driver wait. Default lookup timeout is used. |
|
| 386 | 387 | * |
| 387 | 388 | * @param context |
| 388 | 389 | * The search context to use for the search. |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/AdminConsolePage.java
| ... | ... | @@ -22,6 +22,7 @@ import com.sap.sailing.selenium.pages.adminconsole.event.EventConfigurationPanel |
| 22 | 22 | import com.sap.sailing.selenium.pages.adminconsole.igtimi.IgtimiAccountsManagementPanelPO; |
| 23 | 23 | import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; |
| 24 | 24 | import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardGroupConfigurationPanelPO; |
| 25 | +import com.sap.sailing.selenium.pages.adminconsole.racemanagementapp.RaceManagementAppPanelPO; |
|
| 25 | 26 | import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaStructureManagementPanelPO; |
| 26 | 27 | import com.sap.sailing.selenium.pages.adminconsole.roles.RoleDefinitionsPanelPO; |
| 27 | 28 | import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesBoatsPanelPO; |
| ... | ... | @@ -49,6 +50,9 @@ public class AdminConsolePage extends HostPageWithAuthentication { |
| 49 | 50 | private static final String EVENTS_TAB_LABEL = "Events"; //$NON-NLS-1$ |
| 50 | 51 | private static final String EVENTS_TAB_IDENTIFIER = "EventManagement"; //$NON-NLS-1$ |
| 51 | 52 | |
| 53 | + private static final String RACE_MANAGER_APP_TAB_LABEL = "Race Manager App"; //$NON-NLS-1$ |
|
| 54 | + private static final String RACE_MANAGER_APP_TAB_IDENTIFIER = "RaceCommiteeAppPanel"; //$NON-NLS-1$ |
|
| 55 | + |
|
| 52 | 56 | private static final String REGATTA_STRUCTURE_TAB_LABEL = "Regattas"; //$NON-NLS-1$ |
| 53 | 57 | private static final String REGATTA_STRUCTURE_TAB_IDENTIFIER = "RegattaStructureManagement"; //$NON-NLS-1$ |
| 54 | 58 | |
| ... | ... | @@ -160,6 +164,10 @@ public class AdminConsolePage extends HostPageWithAuthentication { |
| 160 | 164 | return new EventConfigurationPanelPO(this.driver, goToTab(EVENTS_TAB_LABEL, EVENTS_TAB_IDENTIFIER, true)); |
| 161 | 165 | } |
| 162 | 166 | |
| 167 | + public RaceManagementAppPanelPO goToRaceManagerApp() { |
|
| 168 | + return new RaceManagementAppPanelPO(this.driver, goToTab(RACE_MANAGER_APP_TAB_LABEL, RACE_MANAGER_APP_TAB_IDENTIFIER, true)); |
|
| 169 | + } |
|
| 170 | + |
|
| 163 | 171 | public UserManagementPanelPO goToUserManagement() { |
| 164 | 172 | goToTab(ADVANCED_PARENT_LABEL, ADVANCED_TAB_PARENT_IDENTIFIER, true); |
| 165 | 173 | return new UserManagementPanelPO(this.driver, |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/connectors/SmartphoneTrackingEventManagementPanelPO.java
| ... | ... | @@ -11,8 +11,14 @@ import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesListPO; |
| 11 | 11 | import com.sap.sailing.selenium.pages.gwt.CellTablePO; |
| 12 | 12 | import com.sap.sailing.selenium.pages.gwt.DataEntryPO; |
| 13 | 13 | import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO; |
| 14 | +import com.sap.sailing.selenium.test.adminconsole.smartphonetracking.MapDevicesDialogPO; |
|
| 15 | +import com.sap.sailing.selenium.test.adminconsole.smartphonetracking.RegisterCompetitorsDialogPO; |
|
| 14 | 16 | |
| 15 | 17 | public class SmartphoneTrackingEventManagementPanelPO extends PageArea { |
| 18 | + |
|
| 19 | + private static final String ACTION_COMPETITOR_REGISTRATIONS = "ACTION_COMPETITOR_REGISTRATIONS"; |
|
| 20 | + private static final String ACTION_MAP_DEVICES = "ACTION_MAP_DEVICES"; |
|
| 21 | + |
|
| 16 | 22 | @FindBy(how = BySeleniumId.class, using = "AvailableLeaderboardsTable") |
| 17 | 23 | private WebElement leaderboardTable; |
| 18 | 24 | |
| ... | ... | @@ -45,4 +51,14 @@ public class SmartphoneTrackingEventManagementPanelPO extends PageArea { |
| 45 | 51 | leaderboardRefreshButton.click(); |
| 46 | 52 | waitForAjaxRequests(); |
| 47 | 53 | } |
| 54 | + |
|
| 55 | + public RegisterCompetitorsDialogPO pushCompetitorRegistrationsActionButton(DataEntryPO aLeaderboard) { |
|
| 56 | + aLeaderboard.clickActionImage(ACTION_COMPETITOR_REGISTRATIONS); |
|
| 57 | + return this.waitForPO(RegisterCompetitorsDialogPO::new, "registerCompetitorsDialog"); |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + public MapDevicesDialogPO pushMapDevicesActionButton(DataEntryPO aLeaderboard) { |
|
| 61 | + aLeaderboard.clickActionImage(ACTION_MAP_DEVICES); |
|
| 62 | + return this.waitForPO(MapDevicesDialogPO::new, "regattaLogTrackingDeviceMappingsDialog"); |
|
| 63 | + } |
|
| 48 | 64 | } |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/racemanagementapp/DeviceConfigurationCreateDialogPO.java
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +package com.sap.sailing.selenium.pages.adminconsole.racemanagementapp; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 7 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 8 | +import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
|
| 9 | + |
|
| 10 | +public class DeviceConfigurationCreateDialogPO extends DataEntryDialogPO { |
|
| 11 | + |
|
| 12 | + @FindBy(how = BySeleniumId.class, using = "raceManagerDeviceName") |
|
| 13 | + private WebElement nameField; |
|
| 14 | + |
|
| 15 | + protected DeviceConfigurationCreateDialogPO(WebDriver driver, WebElement element) { |
|
| 16 | + super(driver, element); |
|
| 17 | + } |
|
| 18 | + |
|
| 19 | + public void setDeviceName(String name) { |
|
| 20 | + nameField.sendKeys(name); |
|
| 21 | + } |
|
| 22 | + |
|
| 23 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/racemanagementapp/DeviceConfigurationDetailsAreaPO.java
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +package com.sap.sailing.selenium.pages.adminconsole.racemanagementapp; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 7 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 8 | +import com.sap.sailing.selenium.pages.PageArea; |
|
| 9 | + |
|
| 10 | +public class DeviceConfigurationDetailsAreaPO extends PageArea { |
|
| 11 | + |
|
| 12 | + public static final String DEVICE_CONFIGURATION_QR_CODE_DIALOG_ID = "DeviceConfigurationQRIdentifierDialog"; |
|
| 13 | + |
|
| 14 | + @FindBy(how = BySeleniumId.class, using = "DeviceConfigurationDetailsQrCodeButton") |
|
| 15 | + private WebElement deviceConfigurationDetailsQrCodeButton; |
|
| 16 | + |
|
| 17 | + public DeviceConfigurationDetailsAreaPO(WebDriver driver, WebElement element) { |
|
| 18 | + super(driver, element); |
|
| 19 | + } |
|
| 20 | + |
|
| 21 | + public DeviceConfigurationQRCodeDialogPO openQRCodeDialog() { |
|
| 22 | + deviceConfigurationDetailsQrCodeButton.click(); |
|
| 23 | + return getPO(DeviceConfigurationQRCodeDialogPO::new, DEVICE_CONFIGURATION_QR_CODE_DIALOG_ID); |
|
| 24 | + } |
|
| 25 | + |
|
| 26 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/racemanagementapp/DeviceConfigurationQRCodeDialogPO.java
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +package com.sap.sailing.selenium.pages.adminconsole.racemanagementapp; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 7 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 8 | +import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
|
| 9 | + |
|
| 10 | +public class DeviceConfigurationQRCodeDialogPO extends DataEntryDialogPO { |
|
| 11 | + |
|
| 12 | + @FindBy(how = BySeleniumId.class, using = "QRIdentifierURL") |
|
| 13 | + private WebElement url; |
|
| 14 | + |
|
| 15 | + protected DeviceConfigurationQRCodeDialogPO(WebDriver driver, WebElement element) { |
|
| 16 | + super(driver, element); |
|
| 17 | + } |
|
| 18 | + |
|
| 19 | + public String getUrl() { |
|
| 20 | + return url.getText(); |
|
| 21 | + } |
|
| 22 | + |
|
| 23 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/racemanagementapp/DeviceConfigurationsTablePO.java
| ... | ... | @@ -0,0 +1,34 @@ |
| 1 | +package com.sap.sailing.selenium.pages.adminconsole.racemanagementapp; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.pages.gwt.CellTablePO; |
|
| 7 | +import com.sap.sailing.selenium.pages.gwt.DataEntryPO; |
|
| 8 | +import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO; |
|
| 9 | + |
|
| 10 | +public class DeviceConfigurationsTablePO extends GenericCellTablePO<DeviceConfigurationsTablePO.DeviceConfigurationEntryPO> { |
|
| 11 | + |
|
| 12 | + public static class DeviceConfigurationEntryPO extends DataEntryPO { |
|
| 13 | + |
|
| 14 | + public DeviceConfigurationEntryPO(CellTablePO<?> table, WebElement element) { |
|
| 15 | + super(table, element); |
|
| 16 | + } |
|
| 17 | + |
|
| 18 | + @Override |
|
| 19 | + public Object getIdentifier() { |
|
| 20 | + return getColumnContent("Device"); |
|
| 21 | + } |
|
| 22 | + |
|
| 23 | + } |
|
| 24 | + |
|
| 25 | + public DeviceConfigurationsTablePO(WebDriver driver, WebElement element) { |
|
| 26 | + super(driver, element, DeviceConfigurationEntryPO.class); |
|
| 27 | + } |
|
| 28 | + |
|
| 29 | + @Override |
|
| 30 | + protected DeviceConfigurationEntryPO createDataEntry(WebElement element) { |
|
| 31 | + return new DeviceConfigurationEntryPO(this, element); |
|
| 32 | + } |
|
| 33 | + |
|
| 34 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/racemanagementapp/RaceManagementAppPanelPO.java
| ... | ... | @@ -0,0 +1,44 @@ |
| 1 | +package com.sap.sailing.selenium.pages.adminconsole.racemanagementapp; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 7 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 8 | +import com.sap.sailing.selenium.pages.PageArea; |
|
| 9 | +import com.sap.sailing.selenium.pages.adminconsole.racemanagementapp.DeviceConfigurationsTablePO.DeviceConfigurationEntryPO; |
|
| 10 | +import com.sap.sailing.selenium.pages.gwt.CellTablePO; |
|
| 11 | +import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO; |
|
| 12 | + |
|
| 13 | +public class RaceManagementAppPanelPO extends PageArea { |
|
| 14 | + |
|
| 15 | + private static final String DEVICE_CONGIGURATION_DETAILS_AREA_ID = "DeviceConfigurationDetailComposite"; |
|
| 16 | + private static final String NEW_DEVICE_CONFIGURATION_DIALOG_ID = "SelectNameForNewDeviceConfigurationDialog"; |
|
| 17 | + |
|
| 18 | + @FindBy(how = BySeleniumId.class, using = "addDeviceConfigurationButton") |
|
| 19 | + private WebElement addDeviceConfigurationButton; |
|
| 20 | + |
|
| 21 | + @FindBy(how = BySeleniumId.class, using = "DeviceConfigurationList") |
|
| 22 | + private WebElement deviceConfigurationListTable; |
|
| 23 | + |
|
| 24 | + public RaceManagementAppPanelPO(WebDriver driver, WebElement element) { |
|
| 25 | + super(driver, element); |
|
| 26 | + } |
|
| 27 | + |
|
| 28 | + public DeviceConfigurationCreateDialogPO createDeviceConfiguration() { |
|
| 29 | + addDeviceConfigurationButton.click(); |
|
| 30 | + return getPO(DeviceConfigurationCreateDialogPO::new, NEW_DEVICE_CONFIGURATION_DIALOG_ID); |
|
| 31 | + } |
|
| 32 | + |
|
| 33 | + public DeviceConfigurationDetailsAreaPO getDeviceConfigurationDetails() { |
|
| 34 | + DeviceConfigurationEntryPO deviceConfiguration = getLeaderboardTable().getEntries().get(0); |
|
| 35 | + deviceConfiguration.select(); |
|
| 36 | + return getPO(DeviceConfigurationDetailsAreaPO::new, DEVICE_CONGIGURATION_DETAILS_AREA_ID); |
|
| 37 | + } |
|
| 38 | + |
|
| 39 | + |
|
| 40 | + public CellTablePO<DeviceConfigurationEntryPO> getLeaderboardTable() { |
|
| 41 | + return new GenericCellTablePO<>(this.driver, this.deviceConfigurationListTable, DeviceConfigurationEntryPO.class); |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java
| ... | ... | @@ -34,6 +34,9 @@ public class RegattaDetailsCompositePO extends PageArea { |
| 34 | 34 | @FindBy(how = BySeleniumId.class, using = "SeriesCellTable") |
| 35 | 35 | private WebElement seriesTable; |
| 36 | 36 | |
| 37 | + @FindBy(how = BySeleniumId.class, using = "registrationLinkWithQRCodeOpenButton") |
|
| 38 | + private WebElement registrationLinkWithQRCodeOpenButton; |
|
| 39 | + |
|
| 37 | 40 | public RegattaDetailsCompositePO(WebDriver driver, WebElement element) { |
| 38 | 41 | super(driver, element); |
| 39 | 42 | } |
| ... | ... | @@ -107,4 +110,10 @@ public class RegattaDetailsCompositePO extends PageArea { |
| 107 | 110 | waitForAjaxRequests(); |
| 108 | 111 | waitUntil(() -> Objects.equal(getRaceNames(series), races)); |
| 109 | 112 | } |
| 113 | + |
|
| 114 | + public RegistrationLinkWithQRCodeDialogPO configureRegistrationURL() { |
|
| 115 | + registrationLinkWithQRCodeOpenButton.click(); |
|
| 116 | + WebElement dialog = findElementBySeleniumId(this.driver, "RegistrationLinkWithQRCodeDialog"); |
|
| 117 | + return new RegistrationLinkWithQRCodeDialogPO(this.driver, dialog); |
|
| 118 | + } |
|
| 110 | 119 | } |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaEditDialogPO.java
| ... | ... | @@ -7,6 +7,8 @@ import com.sap.sailing.selenium.core.BySeleniumId; |
| 7 | 7 | import com.sap.sailing.selenium.core.FindBy; |
| 8 | 8 | import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
| 9 | 9 | import com.sap.sailing.selenium.pages.gwt.CheckBoxPO; |
| 10 | +import com.sap.sailing.selenium.pages.gwt.ListBoxPO; |
|
| 11 | +import com.sap.sailing.selenium.pages.gwt.TextBoxPO; |
|
| 10 | 12 | |
| 11 | 13 | public class RegattaEditDialogPO extends DataEntryDialogPO { |
| 12 | 14 | @FindBy(how = BySeleniumId.class, using = "RacingProcedureConfigurationCheckBox") |
| ... | ... | @@ -30,8 +32,8 @@ public class RegattaEditDialogPO extends DataEntryDialogPO { |
| 30 | 32 | @FindBy(how = BySeleniumId.class, using = "AddSeriesButton") |
| 31 | 33 | private WebElement addSeriesButton; |
| 32 | 34 | |
| 33 | - @FindBy(how = BySeleniumId.class, using = "registrationLinkWithQRCodeOpenButton") |
|
| 34 | - private WebElement registrationLinkWithQRCodeOpenButton; |
|
| 35 | + @FindBy(how = BySeleniumId.class, using = "SecretTextBox") |
|
| 36 | + private WebElement secretTextBox; |
|
| 35 | 37 | |
| 36 | 38 | public RegattaEditDialogPO(WebDriver driver, WebElement element) { |
| 37 | 39 | super(driver, element); |
| ... | ... | @@ -53,15 +55,18 @@ public class RegattaEditDialogPO extends DataEntryDialogPO { |
| 53 | 55 | return new SeriesCreateDialogPO(this.driver, dialog); |
| 54 | 56 | } |
| 55 | 57 | |
| 56 | - public RegistrationLinkWithQRCodeDialogPO configureRegistrationURL() { |
|
| 57 | - registrationLinkWithQRCodeOpenButton.click(); |
|
| 58 | - WebElement dialog = findElementBySeleniumId(this.driver, "RegistrationLinkWithQRCodeDialog"); |
|
| 59 | - return new RegistrationLinkWithQRCodeDialogPO(this.driver, dialog); |
|
| 60 | - } |
|
| 61 | - |
|
| 62 | 58 | public void addSeries(String seriesName) { |
| 63 | 59 | SeriesCreateDialogPO seriesCreateDialog = addSeries(); |
| 64 | 60 | seriesCreateDialog.setSeriesName(seriesName); |
| 65 | 61 | seriesCreateDialog.pressOk(); |
| 66 | 62 | } |
| 63 | + |
|
| 64 | + public String getSecret() { |
|
| 65 | + return TextBoxPO.create(driver, secretTextBox).getValue(); |
|
| 66 | + } |
|
| 67 | + |
|
| 68 | + public String getSelectedEventId() { |
|
| 69 | + return ListBoxPO.create(driver, eventListBox).getSelectedOptionValue(); |
|
| 70 | + } |
|
| 67 | 71 | } |
| 72 | + |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaStructureManagementPanelPO.java
| ... | ... | @@ -6,6 +6,7 @@ import org.openqa.selenium.WebDriver; |
| 6 | 6 | import org.openqa.selenium.WebElement; |
| 7 | 7 | import org.openqa.selenium.support.ui.WebDriverWait; |
| 8 | 8 | |
| 9 | +import com.sap.sailing.domain.common.CompetitorRegistrationType; |
|
| 9 | 10 | import com.sap.sailing.selenium.core.BySeleniumId; |
| 10 | 11 | import com.sap.sailing.selenium.core.FindBy; |
| 11 | 12 | import com.sap.sailing.selenium.pages.PageArea; |
| ... | ... | @@ -43,7 +44,7 @@ public class RegattaStructureManagementPanelPO extends PageArea { |
| 43 | 44 | * |
| 44 | 45 | * @param regatta |
| 45 | 46 | */ |
| 46 | - public void createRegatta(RegattaDescriptor regatta) { |
|
| 47 | + public void createRegatta(RegattaDescriptor regatta, boolean withDefaultLeaderboard) { |
|
| 47 | 48 | RegattaCreateDialogPO createRegattaDialog = startRegattaCreation(); |
| 48 | 49 | createRegattaDialog.setRegattaName(regatta.getName()+" ("+regatta.getBoatClass()+")"); |
| 49 | 50 | createRegattaDialog.setBoatClass(regatta.getBoatClass()); |
| ... | ... | @@ -54,7 +55,12 @@ public class RegattaStructureManagementPanelPO extends PageArea { |
| 54 | 55 | // QUESTION: How do we handle an error (here or in the dialog)? |
| 55 | 56 | createRegattaDialog.pressOk(); |
| 56 | 57 | DefaultRegattaLeaderboardCreateDialogPO createDefaultRegattaLeaderboardDialog = createDefaultRegattaLeaderboard(); |
| 57 | - createDefaultRegattaLeaderboardDialog.pressCancel(); |
|
| 58 | + if (withDefaultLeaderboard) { |
|
| 59 | + createDefaultRegattaLeaderboardDialog.pressOk(); |
|
| 60 | + } else { |
|
| 61 | + createDefaultRegattaLeaderboardDialog.pressCancel(); |
|
| 62 | + } |
|
| 63 | + |
|
| 58 | 64 | } |
| 59 | 65 | |
| 60 | 66 | public void createRegattaAndAddToEvent(RegattaDescriptor regatta, String event, String[] courseAreaNames) { |
| ... | ... | @@ -63,6 +69,7 @@ public class RegattaStructureManagementPanelPO extends PageArea { |
| 63 | 69 | createRegattaDialog.setBoatClass(regatta.getBoatClass()); |
| 64 | 70 | createRegattaDialog.setCompetitorRegistrationType(regatta.getCompetitorRegistrationType()); |
| 65 | 71 | createRegattaDialog.setEventAndCourseArea(event, courseAreaNames); |
| 72 | + createRegattaDialog.setCompetitorRegistrationType(CompetitorRegistrationType.OPEN_UNMODERATED); |
|
| 66 | 73 | createRegattaDialog.pressOk(); |
| 67 | 74 | createDefaultRegattaLeaderboard().pressOk(); |
| 68 | 75 | waitForPO(ConfirmDialogPO::new, EventConfigurationPanelPO.ID_LINK_LEADERBORAD_TO_GROUP_DIALOG, 5).pressOk(); |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegistrationLinkWithQRCodeDialogPO.java
| ... | ... | @@ -6,6 +6,7 @@ import org.openqa.selenium.WebElement; |
| 6 | 6 | import com.sap.sailing.selenium.core.BySeleniumId; |
| 7 | 7 | import com.sap.sailing.selenium.core.FindBy; |
| 8 | 8 | import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
| 9 | +import com.sap.sailing.selenium.pages.gwt.TextBoxPO; |
|
| 9 | 10 | |
| 10 | 11 | public class RegistrationLinkWithQRCodeDialogPO extends DataEntryDialogPO { |
| 11 | 12 | |
| ... | ... | @@ -15,8 +16,15 @@ public class RegistrationLinkWithQRCodeDialogPO extends DataEntryDialogPO { |
| 15 | 16 | @FindBy(how = BySeleniumId.class, using = "GenerateSecretButton") |
| 16 | 17 | private WebElement generateSecretButton; |
| 17 | 18 | |
| 19 | + @FindBy(how = BySeleniumId.class, using = "RegistrationLinkUrl") |
|
| 20 | + private WebElement registrationLinkUrl; |
|
| 21 | + |
|
| 18 | 22 | protected RegistrationLinkWithQRCodeDialogPO(WebDriver driver, WebElement element) { |
| 19 | 23 | super(driver, element); |
| 20 | 24 | } |
| 25 | + |
|
| 26 | + public String getRegistrationLinkUrl() { |
|
| 27 | + return TextBoxPO.create(driver, registrationLinkUrl).getValue(); |
|
| 28 | + } |
|
| 21 | 29 | |
| 22 | 30 | } |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/common/DataEntryDialogPO.java
| ... | ... | @@ -1,13 +1,11 @@ |
| 1 | 1 | package com.sap.sailing.selenium.pages.common; |
| 2 | 2 | |
| 3 | -import java.util.function.BooleanSupplier; |
|
| 4 | - |
|
| 5 | 3 | import org.openqa.selenium.Alert; |
| 6 | 4 | import org.openqa.selenium.JavascriptExecutor; |
| 7 | -import org.openqa.selenium.StaleElementReferenceException; |
|
| 8 | 5 | import org.openqa.selenium.WebDriver; |
| 9 | 6 | import org.openqa.selenium.WebElement; |
| 10 | 7 | import org.openqa.selenium.support.ui.ExpectedConditions; |
| 8 | +import org.openqa.selenium.support.ui.Wait; |
|
| 11 | 9 | import org.openqa.selenium.support.ui.WebDriverWait; |
| 12 | 10 | |
| 13 | 11 | import com.sap.sailing.selenium.core.BySeleniumId; |
| ... | ... | @@ -65,35 +63,18 @@ public abstract class DataEntryDialogPO extends PageArea { |
| 65 | 63 | public void pressOk(boolean acceptAlert, boolean waitForAjaxRequests) { |
| 66 | 64 | // This generically triggers revalidation in dialogs to ensure that the ok button gets enabled |
| 67 | 65 | ((JavascriptExecutor) driver).executeScript("!!document.activeElement ? document.activeElement.blur() : 0"); |
| 68 | - |
|
| 69 | - scrollToView(this.okButton); |
|
| 70 | - // Browsers may use smooth scrolling |
|
| 71 | - waitUntil(() -> isElementEntirelyVisible(this.okButton) && this.okButton.isEnabled()); |
|
| 72 | - this.okButton.click(); |
|
| 73 | - |
|
| 66 | + Wait<WebDriver> wait = new WebDriverWait(driver, 20); |
|
| 67 | + // click OK |
|
| 68 | + wait.until(ExpectedConditions.elementToBeClickable(okButton)).click(); |
|
| 74 | 69 | if (acceptAlert) { |
| 75 | 70 | final Alert alert = new WebDriverWait(driver, DEFAULT_WAIT_TIMEOUT_SECONDS) |
| 76 | 71 | .until(ExpectedConditions.alertIsPresent()); |
| 77 | 72 | alert.accept(); |
| 78 | 73 | } |
| 79 | - |
|
| 80 | 74 | if (waitForAjaxRequests) { |
| 81 | 75 | // Wait, since we do a callback usually |
| 82 | 76 | waitForAjaxRequests(); |
| 83 | 77 | } |
| 84 | - |
|
| 85 | - // This waits until the dialog is physically closed to make sure further don't fail because the dialog still covers other elements |
|
| 86 | - waitUntil(new BooleanSupplier() { |
|
| 87 | - @Override |
|
| 88 | - public boolean getAsBoolean() { |
|
| 89 | - try { |
|
| 90 | - return !((WebElement) context).isDisplayed(); |
|
| 91 | - } catch (StaleElementReferenceException e) { |
|
| 92 | - // When the element was removed from the DOM, it isn't displayed anymore |
|
| 93 | - return true; |
|
| 94 | - } |
|
| 95 | - } |
|
| 96 | - }); |
|
| 97 | 78 | } |
| 98 | 79 | |
| 99 | 80 | public void pressCancel() { |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestLinkCreation.java
| ... | ... | @@ -0,0 +1,232 @@ |
| 1 | +package com.sap.sailing.selenium.test.adminconsole; |
|
| 2 | + |
|
| 3 | +import java.net.URL; |
|
| 4 | +import java.util.Date; |
|
| 5 | + |
|
| 6 | +import javax.xml.bind.DatatypeConverter; |
|
| 7 | + |
|
| 8 | +import org.hamcrest.Matcher; |
|
| 9 | +import org.hamcrest.MatcherAssert; |
|
| 10 | +import org.hamcrest.Matchers; |
|
| 11 | +import org.junit.Assert; |
|
| 12 | +import org.junit.Assume; |
|
| 13 | +import org.junit.Before; |
|
| 14 | +import org.junit.Test; |
|
| 15 | +import org.openqa.selenium.By; |
|
| 16 | +import org.openqa.selenium.WebDriver; |
|
| 17 | +import org.openqa.selenium.WebElement; |
|
| 18 | +import org.openqa.selenium.support.ui.ExpectedConditions; |
|
| 19 | +import org.openqa.selenium.support.ui.Wait; |
|
| 20 | +import org.openqa.selenium.support.ui.WebDriverWait; |
|
| 21 | +import org.slf4j.Logger; |
|
| 22 | +import org.slf4j.LoggerFactory; |
|
| 23 | + |
|
| 24 | +import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; |
|
| 25 | +import com.sap.sailing.selenium.pages.adminconsole.connectors.SmartphoneTrackingEventManagementPanelPO; |
|
| 26 | +import com.sap.sailing.selenium.pages.adminconsole.event.EventConfigurationPanelPO; |
|
| 27 | +import com.sap.sailing.selenium.pages.adminconsole.racemanagementapp.DeviceConfigurationCreateDialogPO; |
|
| 28 | +import com.sap.sailing.selenium.pages.adminconsole.racemanagementapp.DeviceConfigurationDetailsAreaPO; |
|
| 29 | +import com.sap.sailing.selenium.pages.adminconsole.racemanagementapp.DeviceConfigurationQRCodeDialogPO; |
|
| 30 | +import com.sap.sailing.selenium.pages.adminconsole.racemanagementapp.RaceManagementAppPanelPO; |
|
| 31 | +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaDetailsCompositePO; |
|
| 32 | +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaEditDialogPO; |
|
| 33 | +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; |
|
| 34 | +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaStructureManagementPanelPO; |
|
| 35 | +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegistrationLinkWithQRCodeDialogPO; |
|
| 36 | +import com.sap.sailing.selenium.pages.gwt.DataEntryPO; |
|
| 37 | +import com.sap.sailing.selenium.pages.home.HomePage; |
|
| 38 | +import com.sap.sailing.selenium.test.AbstractSeleniumTest; |
|
| 39 | +import com.sap.sailing.selenium.test.adminconsole.smartphonetracking.AddCompetitorWithBoatDialogPO; |
|
| 40 | +import com.sap.sailing.selenium.test.adminconsole.smartphonetracking.AddDeviceMappingsDialogPO; |
|
| 41 | +import com.sap.sailing.selenium.test.adminconsole.smartphonetracking.MapDevicesDialogPO; |
|
| 42 | +import com.sap.sailing.selenium.test.adminconsole.smartphonetracking.RegisterCompetitorsDialogPO; |
|
| 43 | + |
|
| 44 | +/** |
|
| 45 | + * There are various link creation logic in AdminConsole. This test is to cover them. |
|
| 46 | + */ |
|
| 47 | +public class TestLinkCreation extends AbstractSeleniumTest { |
|
| 48 | + |
|
| 49 | + private static final Logger LOG = LoggerFactory.getLogger(TestLinkCreation.class); |
|
| 50 | + |
|
| 51 | + private static final String DEVICE_CONFIG_NAME = "Test"; |
|
| 52 | + private static final String BMW_CUP_EVENT = "BMW Cup"; |
|
| 53 | + private static final String BMW_CUP_BOAT_CLASS = "J80"; |
|
| 54 | + private static final String AUDI_CUP_BOAT_CLASS = "J70"; |
|
| 55 | + private static final String BMW_CUP_REGATTA = "BMW Cup (J80)"; //$NON-NLS-1$ |
|
| 56 | + private static final String AUDI_CUP_REGATTA = "Audi Business Cup (J70)"; //$NON-NLS-1$ |
|
| 57 | + private static final String BMW_CUP_EVENTS_DESC = "BMW Cup Description"; |
|
| 58 | + private static final String BMW_VENUE = "Somewhere"; |
|
| 59 | + private static final Date BMW_START_EVENT_TIME = DatatypeConverter.parseDateTime("2012-04-08T10:09:00-05:00") |
|
| 60 | + .getTime(); |
|
| 61 | + private static final Date BMW_STOP_EVENT_TIME = DatatypeConverter.parseDateTime("2017-04-08T10:50:00-05:00") |
|
| 62 | + .getTime(); |
|
| 63 | + private static final String CUSTOM_COURSE_AREA = "Custom X"; |
|
| 64 | + private static final String INVITATION_URL_BASE = "https://sailinsight30-app.sapsailing.com/publicInvite?regatta_name=Audi+Business+Cup+(J70)+(J70)"; |
|
| 65 | + private static final String INVITATION_QR_CODE_BASE = "https://sailinsight30-app.sapsailing.com/publicInvite?regatta_name=Audi%20Business%20Cup%20(J70)%20(J70)"; |
|
| 66 | + private static final String EXPECTED_PUPLIC_INVITE_QR_CODE_TITLE = "Welcome to the public regatta Audi Business Cup (J70) (J70)"; |
|
| 67 | + private static final String EXPECTED_RACE_MANAGER_APP_QR_CODE_TITLE = "Welcome to the Race Manager App registration"; |
|
| 68 | + private static final String EXPECTED_DEVICE_REGISTRATION_QR_CODE_TITLE = "Welcome Competitor Test to the regatta Audi Business Cup (J70) (J70)"; |
|
| 69 | + private static final String EXPECTED_QR_LINK_TEXT = "Please scan this QR Code with your mobile device to proceed with the registration"; |
|
| 70 | + |
|
| 71 | + private static final String CHECK_RACE_APP_URL_REGEX = "^https:\\/\\/racemanager-app.sapsailing.com\\/invite\\" |
|
| 72 | + + "?server_url=http.*localhost.*(\\d{0,5})?" + "&device_config_identifier=" + DEVICE_CONFIG_NAME |
|
| 73 | + + "&device_config_uuid=(\\w|\\d|-)*"; |
|
| 74 | + |
|
| 75 | + private static final String DEVICE_REGISTRATION_URL_REGEX = ".*https:\\/\\/sailinsight30-app.sapsailing.com\\/invite\\?checkinUrl=.*"; |
|
| 76 | + |
|
| 77 | + @Override |
|
| 78 | + @Before |
|
| 79 | + public void setUp() { |
|
| 80 | + boolean checkUrlIsWorking; |
|
| 81 | + String prodCheckUrl = "https://www.sapsailing.com/gwt/status"; |
|
| 82 | + try { |
|
| 83 | + LOG.debug("Testing check URL from productive server"); |
|
| 84 | + (new URL(prodCheckUrl)).openStream().close(); |
|
| 85 | + checkUrlIsWorking = true; |
|
| 86 | + LOG.info("Productive server {} is accessible.", prodCheckUrl); |
|
| 87 | + } catch (Exception ex) { |
|
| 88 | + checkUrlIsWorking = false; |
|
| 89 | + LOG.warn("Productive server {} is NOT accessible. Skip tests which are requirering live connection.", |
|
| 90 | + prodCheckUrl); |
|
| 91 | + } |
|
| 92 | + Assume.assumeTrue("Execute link creation test only if productive server is online (www.sapsailing.com).", |
|
| 93 | + checkUrlIsWorking); |
|
| 94 | + clearState(getContextRoot()); |
|
| 95 | + super.setUp(); |
|
| 96 | + } |
|
| 97 | + |
|
| 98 | + /** |
|
| 99 | + * Test the creation of an invitation link. |
|
| 100 | + * <p> |
|
| 101 | + * Please notice, that the test checks the created invitation link by calling it and checking over the redirects |
|
| 102 | + * from branch.io over production environment (my.sapsailing.com) back to localhost. The link back to localhost need |
|
| 103 | + * an additional confirmation on production server. |
|
| 104 | + */ |
|
| 105 | + @Test |
|
| 106 | + public void testRegattaOverviewInvitationLinkCreation() { |
|
| 107 | + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
|
| 108 | + // create an event and regatta |
|
| 109 | + EventConfigurationPanelPO events = adminConsole.goToEvents(); |
|
| 110 | + events.createEventWithDefaultLeaderboardGroupRegattaAndDefaultLeaderboard(BMW_CUP_EVENT, BMW_CUP_EVENTS_DESC, |
|
| 111 | + BMW_VENUE, BMW_START_EVENT_TIME, BMW_STOP_EVENT_TIME, /* isPublic */ true, BMW_CUP_REGATTA, |
|
| 112 | + BMW_CUP_BOAT_CLASS, BMW_START_EVENT_TIME, BMW_STOP_EVENT_TIME, /* useOverallLeaderboard */ false, |
|
| 113 | + CUSTOM_COURSE_AREA); |
|
| 114 | + RegattaStructureManagementPanelPO regattas = adminConsole.goToRegattaStructure(); |
|
| 115 | + RegattaDescriptor regattaDescriptor = new RegattaDescriptor(AUDI_CUP_REGATTA, AUDI_CUP_BOAT_CLASS); |
|
| 116 | + regattas.createRegattaAndAddToEvent(regattaDescriptor, BMW_CUP_EVENT, new String[] { CUSTOM_COURSE_AREA }); |
|
| 117 | + // extract secret and event ID |
|
| 118 | + RegattaEditDialogPO editRegatta = regattas.getRegattaList().editRegatta(regattaDescriptor); |
|
| 119 | + String secret = editRegatta.getSecret(); |
|
| 120 | + String selectedEventId = editRegatta.getSelectedEventId(); |
|
| 121 | + editRegatta.clickOkButtonOrThrow(); |
|
| 122 | + // create expected link URL based on the secret and event ID |
|
| 123 | + RegattaDetailsCompositePO regattaDetails = regattas.getRegattaDetails(); |
|
| 124 | + RegistrationLinkWithQRCodeDialogPO registrationLinkWithQRCode = regattaDetails.configureRegistrationURL(); |
|
| 125 | + // check URL |
|
| 126 | + String createdInvitationUrl = registrationLinkWithQRCode.getRegistrationLinkUrl(); |
|
| 127 | + Assert.assertTrue(createdInvitationUrl.startsWith(INVITATION_URL_BASE)); |
|
| 128 | + Assert.assertTrue(createdInvitationUrl.contains("secret=" + secret)); |
|
| 129 | + Assert.assertTrue(createdInvitationUrl.contains("event_id=" + selectedEventId)); |
|
| 130 | + Assert.assertTrue(createdInvitationUrl.contains("server=http%3A%2F%2Flocalhost%3A")); |
|
| 131 | + registrationLinkWithQRCode.clickOkButtonOrThrow(); |
|
| 132 | + HomePage.goToHomeUrl(getWebDriver(), createdInvitationUrl); |
|
| 133 | + Wait<WebDriver> wait = new WebDriverWait(getWebDriver(), 30); |
|
| 134 | + // confirm dialog on my.sapsailing.com to redirect back to localhost |
|
| 135 | + wait.until(ExpectedConditions.numberOfElementsToBe( |
|
| 136 | + By.xpath("//button[contains(text(), 'Yes')] | //button[contains(text(), 'Ja')]"), 1)).get(0).click(); |
|
| 137 | + // here we are back on localhost (Home.html#QRCodePlace) |
|
| 138 | + wait.until(ExpectedConditions.presenceOfElementLocated( |
|
| 139 | + By.xpath("//div[contains(text(), '" + EXPECTED_PUPLIC_INVITE_QR_CODE_TITLE + "')]"))); |
|
| 140 | + WebElement qrCodeLink = wait.until(ExpectedConditions |
|
| 141 | + .presenceOfElementLocated(By.xpath("//a[contains(text(), '" + EXPECTED_QR_LINK_TEXT + "')]"))); |
|
| 142 | + Assert.assertTrue(qrCodeLink.getAttribute("href").startsWith(INVITATION_QR_CODE_BASE)); |
|
| 143 | + Assert.assertTrue(qrCodeLink.getAttribute("href").contains("secret=" + secret)); |
|
| 144 | + Assert.assertTrue(qrCodeLink.getAttribute("href").contains("server=http%3A%2F%2Flocalhost%3A")); |
|
| 145 | + } |
|
| 146 | + |
|
| 147 | + /** |
|
| 148 | + * Testing the generation of an invitation QR code for the Race Manager App. |
|
| 149 | + */ |
|
| 150 | + @Test |
|
| 151 | + public void testRaceManagerAppInvitationLink() { |
|
| 152 | + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
|
| 153 | + RaceManagementAppPanelPO raceManagerApp = adminConsole.goToRaceManagerApp(); |
|
| 154 | + DeviceConfigurationCreateDialogPO createDeviceConfiguration = raceManagerApp.createDeviceConfiguration(); |
|
| 155 | + createDeviceConfiguration.setDeviceName(DEVICE_CONFIG_NAME); |
|
| 156 | + createDeviceConfiguration.clickOkButtonOrThrow(); |
|
| 157 | + DeviceConfigurationDetailsAreaPO deviceConfigurationDetails = raceManagerApp.getDeviceConfigurationDetails(); |
|
| 158 | + DeviceConfigurationQRCodeDialogPO qrCodeDialog = deviceConfigurationDetails.openQRCodeDialog(); |
|
| 159 | + Matcher<String> matcher = Matchers.matchesRegex(CHECK_RACE_APP_URL_REGEX); |
|
| 160 | + String createdInvitationUrl = qrCodeDialog.getUrl(); |
|
| 161 | + MatcherAssert.assertThat("Check URL", matcher.matches(createdInvitationUrl)); |
|
| 162 | + HomePage.goToHomeUrl(getWebDriver(), createdInvitationUrl); |
|
| 163 | + // confirm dialog on my.sapsailing.com to redirect back to localhost |
|
| 164 | + Wait<WebDriver> wait = new WebDriverWait(getWebDriver(), 30); |
|
| 165 | + wait.until(ExpectedConditions.numberOfElementsToBe( |
|
| 166 | + By.xpath("//button[contains(text(), 'Yes')] | //button[contains(text(), 'Ja')]"), 1)).get(0).click(); |
|
| 167 | + // here we are back on localhost (Home.html#QRCodePlace) |
|
| 168 | + wait.until(ExpectedConditions.presenceOfElementLocated( |
|
| 169 | + By.xpath("//div[contains(text(), '" + EXPECTED_RACE_MANAGER_APP_QR_CODE_TITLE + "')]"))); |
|
| 170 | + WebElement qrCodeLink = wait.until(ExpectedConditions |
|
| 171 | + .presenceOfElementLocated(By.xpath("//a[contains(text(), '" + EXPECTED_QR_LINK_TEXT + "')]"))); |
|
| 172 | + MatcherAssert.assertThat("Check URL", matcher.matches(qrCodeLink.getAttribute("href"))); |
|
| 173 | + } |
|
| 174 | + |
|
| 175 | + /** |
|
| 176 | + * Device registration via QR code test. |
|
| 177 | + */ |
|
| 178 | + @Test |
|
| 179 | + public void testDeviceRegistation() throws InterruptedException { |
|
| 180 | + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
|
| 181 | + // create an event and regatta |
|
| 182 | + EventConfigurationPanelPO events = adminConsole.goToEvents(); |
|
| 183 | + events.createEventWithDefaultLeaderboardGroupRegattaAndDefaultLeaderboard(BMW_CUP_EVENT, BMW_CUP_EVENTS_DESC, |
|
| 184 | + BMW_VENUE, BMW_START_EVENT_TIME, BMW_STOP_EVENT_TIME, /* isPublic */ true, BMW_CUP_REGATTA, |
|
| 185 | + BMW_CUP_BOAT_CLASS, BMW_START_EVENT_TIME, BMW_STOP_EVENT_TIME, /* useOverallLeaderboard */ false, |
|
| 186 | + CUSTOM_COURSE_AREA); |
|
| 187 | + RegattaStructureManagementPanelPO regattas = adminConsole.goToRegattaStructure(); |
|
| 188 | + RegattaDescriptor regattaDescriptor = new RegattaDescriptor(AUDI_CUP_REGATTA, AUDI_CUP_BOAT_CLASS); |
|
| 189 | + regattas.createRegattaAndAddToEvent(regattaDescriptor, BMW_CUP_EVENT, new String[] { CUSTOM_COURSE_AREA }); |
|
| 190 | + SmartphoneTrackingEventManagementPanelPO smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); |
|
| 191 | + DataEntryPO aLeaderboard = smartphoneTrackingPanel.getLeaderboardTable().getEntries().get(0); |
|
| 192 | + // Add a competitor with boat as precondition for adding devices |
|
| 193 | + RegisterCompetitorsDialogPO registerCompetitorsDialogPO = smartphoneTrackingPanel |
|
| 194 | + .pushCompetitorRegistrationsActionButton(aLeaderboard); |
|
| 195 | + AddCompetitorWithBoatDialogPO addCompetitorWithBoatDialogPO = registerCompetitorsDialogPO |
|
| 196 | + .openAddCompetitorWithBoatDialog(); |
|
| 197 | + addCompetitorWithBoatDialogPO.addCompetitorWithBoat(); |
|
| 198 | + Wait<WebDriver> wait = new WebDriverWait(getWebDriver(), 30); |
|
| 199 | + // wait until competitor is shown in the table. Sometimes test breaks here if dialog was closed to early |
|
| 200 | + wait.until((s) -> registerCompetitorsDialogPO.getCompetitorTable().getEntries().size() > 0); |
|
| 201 | + // now close the dialog |
|
| 202 | + registerCompetitorsDialogPO.clickOkButtonOrThrow(); |
|
| 203 | + // Add device |
|
| 204 | + aLeaderboard = smartphoneTrackingPanel.getLeaderboardTable().getEntries().get(0); |
|
| 205 | + MapDevicesDialogPO mapDevicesDialog = smartphoneTrackingPanel.pushMapDevicesActionButton(aLeaderboard); |
|
| 206 | + AddDeviceMappingsDialogPO addDeviceMappingsDialog = mapDevicesDialog.addMapping(); |
|
| 207 | + // check URL based on boat selection |
|
| 208 | + addDeviceMappingsDialog.getBoatsTable().getEntries().get(0).select(); |
|
| 209 | + String boatUrlPattern = DEVICE_REGISTRATION_URL_REGEX + "boat_id.*"; |
|
| 210 | + Matcher<String> boatMatcher = Matchers.matchesRegex(boatUrlPattern); |
|
| 211 | + MatcherAssert.assertThat("Check URL", |
|
| 212 | + boatMatcher.matches(addDeviceMappingsDialog.getQrCodeUrl(boatUrlPattern))); |
|
| 213 | + // check URL based on competitor selection |
|
| 214 | + addDeviceMappingsDialog.getCompetitorTable().getEntries().get(0).select(); |
|
| 215 | + String compatitorUrlPattern = DEVICE_REGISTRATION_URL_REGEX + "competitor_id.*"; |
|
| 216 | + Matcher<String> competitorMater = Matchers.matchesRegex(compatitorUrlPattern); |
|
| 217 | + String qrCodeUrl = addDeviceMappingsDialog.getQrCodeUrl(compatitorUrlPattern); |
|
| 218 | + MatcherAssert.assertThat("Check URL", competitorMater.matches(qrCodeUrl)); |
|
| 219 | + // check redirect |
|
| 220 | + getWebDriver().get(qrCodeUrl); |
|
| 221 | + // confirm dialog on my.sapsailing.com to redirect back to localhost |
|
| 222 | + wait.until(ExpectedConditions.numberOfElementsToBe( |
|
| 223 | + By.xpath("//button[contains(text(), 'Yes')] | //button[contains(text(), 'Ja')]"), 1)).get(0).click(); |
|
| 224 | + // here we are back on localhost (Home.html#QRCodePlace) |
|
| 225 | + wait.until(ExpectedConditions.presenceOfElementLocated( |
|
| 226 | + By.xpath("//div[contains(text(), '" + EXPECTED_DEVICE_REGISTRATION_QR_CODE_TITLE + "')]"))); |
|
| 227 | + WebElement qrCodeLink = wait.until(ExpectedConditions |
|
| 228 | + .presenceOfElementLocated(By.xpath("//a[contains(text(), '" + EXPECTED_QR_LINK_TEXT + "')]"))); |
|
| 229 | + Matcher<String> matcher = Matchers.matchesRegex(compatitorUrlPattern); |
|
| 230 | + MatcherAssert.assertThat("Check URL", matcher.matches(qrCodeLink.getAttribute("href"))); |
|
| 231 | + } |
|
| 232 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestOpenRegattaCreation.java
| ... | ... | @@ -35,6 +35,6 @@ public class TestOpenRegattaCreation extends AbstractSeleniumTest { |
| 35 | 35 | private void configureRegattaAndLeaderboard() { |
| 36 | 36 | AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
| 37 | 37 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 38 | - regattaStructure.createRegatta(this.regatta); |
|
| 38 | + regattaStructure.createRegatta(this.regatta, /* withDefaultLeaderboard */ false); |
|
| 39 | 39 | } |
| 40 | 40 | } |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java
| ... | ... | @@ -175,7 +175,7 @@ public class TestRefreshableSelectionModel extends AbstractSeleniumTest { |
| 175 | 175 | |
| 176 | 176 | // Create a regatta with 1 series and 5 races as well as a leaderborad |
| 177 | 177 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 178 | - regattaStructure.createRegatta(this.regatta); |
|
| 178 | + regattaStructure.createRegatta(this.regatta, /* withDefaultLeaderboard */ false); |
|
| 179 | 179 | |
| 180 | 180 | RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); |
| 181 | 181 | SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java
| ... | ... | @@ -117,7 +117,7 @@ public class TestSmartphoneTrackingEventManagementPanel extends AbstractSelenium |
| 117 | 117 | |
| 118 | 118 | // Create a regatta with 1 series and 5 races as well as a leaderborad |
| 119 | 119 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 120 | - regattaStructure.createRegatta(this.regatta); |
|
| 120 | + regattaStructure.createRegatta(this.regatta, /* withDefaultLeaderboard */ false); |
|
| 121 | 121 | |
| 122 | 122 | RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); |
| 123 | 123 | SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java
| ... | ... | @@ -61,7 +61,7 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT |
| 61 | 61 | RegattaDescriptor bmwCupDescriptor = new RegattaDescriptor(BMW_CUP_EVENT, BMW_CUP_BOAT_CLASS); |
| 62 | 62 | AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
| 63 | 63 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 64 | - regattaStructure.createRegatta(bmwCupDescriptor); |
|
| 64 | + regattaStructure.createRegatta(bmwCupDescriptor, /* withDefaultLeaderboard */ false); |
|
| 65 | 65 | TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents(); |
| 66 | 66 | tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL); |
| 67 | 67 | tracTracEvents.setReggataForTracking(bmwCupDescriptor); |
| ... | ... | @@ -79,7 +79,7 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT |
| 79 | 79 | public void testStartTrackingWithDefaultReggataWhileReggataForBoatClassExists() { |
| 80 | 80 | AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
| 81 | 81 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 82 | - regattaStructure.createRegatta(new RegattaDescriptor(BMW_CUP_EVENT, BMW_CUP_BOAT_CLASS)); |
|
| 82 | + regattaStructure.createRegatta(new RegattaDescriptor(BMW_CUP_EVENT, BMW_CUP_BOAT_CLASS), /* withDefaultLeaderboard */ false); |
|
| 83 | 83 | TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents(); |
| 84 | 84 | tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL); |
| 85 | 85 | tracTracEvents.setReggataForTracking(DEFAULT_REGATTA); |
| ... | ... | @@ -93,8 +93,8 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT |
| 93 | 93 | RegattaDescriptor idm2013Descriptor = new RegattaDescriptor(IDM_2013_EVENT, IDM_2013_BOAT_CLASS); |
| 94 | 94 | AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
| 95 | 95 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 96 | - regattaStructure.createRegatta(bmwCupDescriptor); |
|
| 97 | - regattaStructure.createRegatta(idm2013Descriptor); |
|
| 96 | + regattaStructure.createRegatta(bmwCupDescriptor, /* withDefaultLeaderboard */ false); |
|
| 97 | + regattaStructure.createRegatta(idm2013Descriptor, /* withDefaultLeaderboard */ false); |
|
| 98 | 98 | TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents(); |
| 99 | 99 | tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL); |
| 100 | 100 | tracTracEvents.setReggataForTracking(idm2013Descriptor); |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/smartphonetracking/AddCompetitorWithBoatDialogPO.java
| ... | ... | @@ -0,0 +1,29 @@ |
| 1 | +package com.sap.sailing.selenium.test.adminconsole.smartphonetracking; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 7 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 8 | +import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
|
| 9 | + |
|
| 10 | +public class AddCompetitorWithBoatDialogPO extends DataEntryDialogPO { |
|
| 11 | + |
|
| 12 | + @FindBy(how = BySeleniumId.class, using = "NameTextBox") |
|
| 13 | + private WebElement nameTextBox; |
|
| 14 | + |
|
| 15 | + @FindBy(how = BySeleniumId.class, using = "SailIdTextBox") |
|
| 16 | + private WebElement sailIdTextBox; |
|
| 17 | + |
|
| 18 | + public AddCompetitorWithBoatDialogPO(WebDriver driver, WebElement element) { |
|
| 19 | + super(driver, element); |
|
| 20 | + } |
|
| 21 | + |
|
| 22 | + public void addCompetitorWithBoat() { |
|
| 23 | + nameTextBox.sendKeys("Competitor Test"); |
|
| 24 | + sailIdTextBox.sendKeys("1234"); |
|
| 25 | + clickOkButtonOrThrow(); |
|
| 26 | + } |
|
| 27 | + |
|
| 28 | + |
|
| 29 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/smartphonetracking/AddDeviceMappingsDialogPO.java
| ... | ... | @@ -0,0 +1,47 @@ |
| 1 | +package com.sap.sailing.selenium.test.adminconsole.smartphonetracking; |
|
| 2 | + |
|
| 3 | +import java.util.regex.Pattern; |
|
| 4 | + |
|
| 5 | +import org.openqa.selenium.WebDriver; |
|
| 6 | +import org.openqa.selenium.WebElement; |
|
| 7 | +import org.openqa.selenium.support.ui.ExpectedConditions; |
|
| 8 | +import org.openqa.selenium.support.ui.Wait; |
|
| 9 | +import org.openqa.selenium.support.ui.WebDriverWait; |
|
| 10 | +import org.slf4j.Logger; |
|
| 11 | +import org.slf4j.LoggerFactory; |
|
| 12 | + |
|
| 13 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 14 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 15 | +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesBoatTablePO; |
|
| 16 | +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorTablePO; |
|
| 17 | +import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
|
| 18 | + |
|
| 19 | +public class AddDeviceMappingsDialogPO extends DataEntryDialogPO { |
|
| 20 | + |
|
| 21 | + Logger logger = LoggerFactory.getLogger(AddDeviceMappingsDialogPO.class); |
|
| 22 | + |
|
| 23 | + @FindBy(how = BySeleniumId.class, using = "QRIdentifierURL") |
|
| 24 | + private WebElement qrCodeUrl; |
|
| 25 | + |
|
| 26 | + public AddDeviceMappingsDialogPO(WebDriver driver, WebElement element) { |
|
| 27 | + super(driver, element); |
|
| 28 | + } |
|
| 29 | + |
|
| 30 | + public TrackedRacesBoatTablePO getBoatsTable() { |
|
| 31 | + Wait<WebDriver> wait = new WebDriverWait(driver, 10); |
|
| 32 | + WebElement boatsTable = wait.until(ExpectedConditions.presenceOfElementLocated(new BySeleniumId("BoatsTable"))); |
|
| 33 | + return new TrackedRacesBoatTablePO(this.driver, boatsTable); |
|
| 34 | + } |
|
| 35 | + |
|
| 36 | + public TrackedRacesCompetitorTablePO getCompetitorTable() { |
|
| 37 | + Wait<WebDriver> wait = new WebDriverWait(driver, 10); |
|
| 38 | + WebElement competitorTable = wait.until(ExpectedConditions.presenceOfElementLocated(new BySeleniumId("CompetitorsTable"))); |
|
| 39 | + return new TrackedRacesCompetitorTablePO(this.driver, competitorTable); |
|
| 40 | + } |
|
| 41 | + |
|
| 42 | + public String getQrCodeUrl(String matcherPattern) { |
|
| 43 | + waitUntil(webDriver -> Pattern.compile(matcherPattern).matcher(qrCodeUrl.getText()).matches()); |
|
| 44 | + return qrCodeUrl.getText(); |
|
| 45 | + } |
|
| 46 | + |
|
| 47 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/smartphonetracking/MapDevicesDialogPO.java
| ... | ... | @@ -0,0 +1,24 @@ |
| 1 | +package com.sap.sailing.selenium.test.adminconsole.smartphonetracking; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 7 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 8 | +import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
|
| 9 | + |
|
| 10 | +public class MapDevicesDialogPO extends DataEntryDialogPO { |
|
| 11 | + |
|
| 12 | + @FindBy(how = BySeleniumId.class, using = "addMappingButton") |
|
| 13 | + private WebElement addMappingButton; |
|
| 14 | + |
|
| 15 | + public MapDevicesDialogPO(WebDriver driver, WebElement element) { |
|
| 16 | + super(driver, element); |
|
| 17 | + } |
|
| 18 | + |
|
| 19 | + public AddDeviceMappingsDialogPO addMapping() { |
|
| 20 | + addMappingButton.click(); |
|
| 21 | + return this.waitForPO(AddDeviceMappingsDialogPO::new, "RegattaLogAddDeviceMappingDialog"); |
|
| 22 | + } |
|
| 23 | + |
|
| 24 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/smartphonetracking/RegisterCompetitorsDialogPO.java
| ... | ... | @@ -0,0 +1,39 @@ |
| 1 | +package com.sap.sailing.selenium.test.adminconsole.smartphonetracking; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | +import org.openqa.selenium.support.ui.ExpectedConditions; |
|
| 6 | +import org.openqa.selenium.support.ui.Wait; |
|
| 7 | +import org.openqa.selenium.support.ui.WebDriverWait; |
|
| 8 | + |
|
| 9 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 10 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 11 | +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorTablePO; |
|
| 12 | +import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; |
|
| 13 | + |
|
| 14 | +public class RegisterCompetitorsDialogPO extends DataEntryDialogPO { |
|
| 15 | + |
|
| 16 | + @FindBy(how = BySeleniumId.class, using = "registerCompetitorsDialog") |
|
| 17 | + private WebElement dialog; |
|
| 18 | + |
|
| 19 | + @FindBy(how = BySeleniumId.class, using = "addCompetitorWithBoatButton") |
|
| 20 | + private WebElement addCompetitorWithBoatButton; |
|
| 21 | + |
|
| 22 | + public RegisterCompetitorsDialogPO(WebDriver driver, WebElement element) { |
|
| 23 | + super(driver, element); |
|
| 24 | + |
|
| 25 | + } |
|
| 26 | + |
|
| 27 | + public AddCompetitorWithBoatDialogPO openAddCompetitorWithBoatDialog() { |
|
| 28 | + this.addCompetitorWithBoatButton.click(); |
|
| 29 | + return getPO(AddCompetitorWithBoatDialogPO::new, "CompetitorWithBoatEditDialog"); |
|
| 30 | + } |
|
| 31 | + |
|
| 32 | + public TrackedRacesCompetitorTablePO getCompetitorTable() { |
|
| 33 | + Wait<WebDriver> wait = new WebDriverWait(driver, 10); |
|
| 34 | + WebElement competitorTable = wait |
|
| 35 | + .until(ExpectedConditions.presenceOfElementLocated(new BySeleniumId("CompetitorsTable"))); |
|
| 36 | + return new TrackedRacesCompetitorTablePO(this.driver, competitorTable); |
|
| 37 | + } |
|
| 38 | + |
|
| 39 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestColumnsInLeaderboard.java
| ... | ... | @@ -124,7 +124,7 @@ public class TestColumnsInLeaderboard extends AbstractSeleniumTest { |
| 124 | 124 | AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
| 125 | 125 | // Create a regatta with 1 series and 5 races as well as a leaderboard |
| 126 | 126 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 127 | - regattaStructure.createRegatta(this.regatta); |
|
| 127 | + regattaStructure.createRegatta(this.regatta, /* withDefaultLeaderboard */ false); |
|
| 128 | 128 | RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); |
| 129 | 129 | SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); |
| 130 | 130 | seriesDialog.addRaces(1, 1); |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java
| ... | ... | @@ -187,7 +187,7 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { |
| 187 | 187 | |
| 188 | 188 | // Create a regatta with 1 series and 5 races as well as a leaderborad |
| 189 | 189 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 190 | - regattaStructure.createRegatta(this.regatta); |
|
| 190 | + regattaStructure.createRegatta(this.regatta, /* withDefaultLeaderboard */ false); |
|
| 191 | 191 | |
| 192 | 192 | RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); |
| 193 | 193 | SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestSwitchingOffStartOfRaceInference.java
| ... | ... | @@ -96,7 +96,7 @@ public class TestSwitchingOffStartOfRaceInference extends AbstractSeleniumTest { |
| 96 | 96 | AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
| 97 | 97 | // Create a regatta with 1 series and 5 races as well as a leaderboard |
| 98 | 98 | RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); |
| 99 | - regattaStructure.createRegatta(this.regatta); |
|
| 99 | + regattaStructure.createRegatta(this.regatta, /* withDefaultLeaderboard */ false); |
|
| 100 | 100 | RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); |
| 101 | 101 | SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); |
| 102 | 102 | seriesDialog.addRaces(3, 3); |
java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceWindJsonSerializer.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.server.gateway.serialization.impl; |
| 2 | 2 | |
| 3 | 3 | import java.util.Iterator; |
| 4 | 4 | import java.util.List; |
| 5 | +import java.util.Set; |
|
| 5 | 6 | import java.util.stream.Collectors; |
| 6 | 7 | |
| 7 | 8 | import org.json.simple.JSONArray; |
| ... | ... | @@ -36,12 +37,13 @@ public class RaceWindJsonSerializer extends AbstractTrackedRaceDataJsonSerialize |
| 36 | 37 | |
| 37 | 38 | @Override |
| 38 | 39 | public JSONObject serialize(TrackedRace trackedRace) { |
| 39 | - final TimePoint finalFrom = trackedRace.getStartOfTracking(); |
|
| 40 | - final TimePoint finalTo = Util.getEarliestOfTimePoints(trackedRace.getEndOfTracking(), |
|
| 41 | - trackedRace.getTimePointOfNewestEvent()); |
|
| 40 | + final TimePoint finalFrom = trackedRace.getStartOfRace(); |
|
| 41 | + final TimePoint finalTo = Util.getEarliestOfTimePoints(trackedRace.getEndOfTracking(), trackedRace.getEndOfRace()); |
|
| 42 | + final Set<WindSource> windSourcesToExclude = trackedRace.getWindSourcesToExclude(); |
|
| 42 | 43 | List<WindSource> highQualityWindSources = trackedRace.getWindSources().stream() |
| 43 | - .filter(windSource -> windSource.getType() == WindSourceType.EXPEDITION |
|
| 44 | + .filter(windSource -> (windSource.getType() == WindSourceType.EXPEDITION |
|
| 44 | 45 | || windSource.getType() == WindSourceType.RACECOMMITTEE) |
| 46 | + && !windSourcesToExclude.contains(windSource)) |
|
| 45 | 47 | .collect(Collectors.toList()); |
| 46 | 48 | JSONArray windSourcesJson = new JSONArray(); |
| 47 | 49 | TimePoint earliestTimePoint = null; |
java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/TrackedRaceJsonSerializer.java
| ... | ... | @@ -35,13 +35,10 @@ public class TrackedRaceJsonSerializer implements JsonSerializer<TrackedRace> { |
| 35 | 35 | |
| 36 | 36 | public JSONObject serialize(TrackedRace trackedRace) { |
| 37 | 37 | JSONObject jsonRace = new JSONObject(); |
| 38 | - |
|
| 39 | 38 | jsonRace.put(FIELD_NAME, trackedRace.getRace().getName()); |
| 40 | 39 | jsonRace.put(FIELD_REGATTA, trackedRace.getRaceIdentifier().getRegattaName()); |
| 41 | - |
|
| 42 | 40 | if (windTrackSerializerProducer != null) { |
| 43 | 41 | JSONArray windTracks = new JSONArray(); |
| 44 | - |
|
| 45 | 42 | List<WindSource> windSources = getAvailableWindSources(trackedRace); |
| 46 | 43 | JSONArray jsonWindSourcesDisplayed = new JSONArray(); |
| 47 | 44 | for (WindSource windSource : windSources) { |
java/com.sap.sailing.server/SailingServer (No Proxy).launch
| ... | ... | @@ -24,7 +24,7 @@ |
| 24 | 24 | <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> |
| 25 | 25 | <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console 12001 -clean"/> |
| 26 | 26 | <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/> |
| 27 | - <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=5010 -Xmx6000m -XX:ThreadPriorityPolicy=2 -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -Dpolardata.source.url=https://www.sapsailing.com -Dwindestimation.source.url=https://www.sapsailing.com -Drestore.tracked.races=true -Dorg.eclipse.jetty.server.Request.maxFormContentSize=50000000 -DAnniversaryRaceDeterminator.enabled=true -Djava.naming.factory.url.pkgs=org.eclipse.jetty.jndi -Djava.naming.factory.initial=org.eclipse.jetty.jndi.InitialContextFactory -Dorg.eclipse.jetty.annotations.maxWait=120 -Dchargebee.site=sailytics-test -Dchargebee.apikey=test_G8QP4NUM1f4MCeZeni1VFC04cdIGZSNyp -Dsubscriptions.disableMailVerificationRequirement=true -Dgwt.rpc.version=9"/> |
|
| 27 | + <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=5010 -Xmx6000m -XX:ThreadPriorityPolicy=2 -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -Dpolardata.source.url=https://www.sapsailing.com -Dwindestimation.source.url=https://www.sapsailing.com -Drestore.tracked.races=true -Dorg.eclipse.jetty.server.Request.maxFormContentSize=50000000 -DAnniversaryRaceDeterminator.enabled=true -Djava.naming.factory.url.pkgs=org.eclipse.jetty.jndi -Djava.naming.factory.initial=org.eclipse.jetty.jndi.InitialContextFactory -Dorg.eclipse.jetty.annotations.maxWait=120 -Dchargebee.site=sailytics-test -Dmanage2sail.accesstoken=${MANAGE2SAIL_ACCESS_TOKEN} -Dchargebee.apikey=test_G8QP4NUM1f4MCeZeni1VFC04cdIGZSNyp -Dsubscriptions.disableMailVerificationRequirement=true -Dgwt.rpc.version=9"/> |
|
| 28 | 28 | <stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/> |
| 29 | 29 | <stringAttribute key="pde.version" value="3.3"/> |
| 30 | 30 | <stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_TENANT_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%"/> |
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/importer/DistanceBasedTwdTransitionImporter.java
| ... | ... | @@ -62,7 +62,7 @@ public class DistanceBasedTwdTransitionImporter { |
| 62 | 62 | // count progress each time the pair's first element changes: |
| 63 | 63 | if (nextFixPair.getA() != previousFirstElementOfPair) { |
| 64 | 64 | windFixNumber++; |
| 65 | - long percent = windFixNumber++ * 100 / numberOfWindFixes; |
|
| 65 | + final long percent = windFixNumber++ * 100 / numberOfWindFixes; |
|
| 66 | 66 | if (percent > oldPercent) { |
| 67 | 67 | LoggingUtil.logInfo("Processing wind fix " + windFixNumber + "/" + numberOfWindFixes + " (" |
| 68 | 68 | + percent + "%) for distance dimension"); |
| ... | ... | @@ -70,10 +70,10 @@ public class DistanceBasedTwdTransitionImporter { |
| 70 | 70 | } |
| 71 | 71 | previousFirstElementOfPair = nextFixPair.getA(); |
| 72 | 72 | } |
| 73 | - double meters = nextFixPair.getA().getPosition().getDistance(nextFixPair.getB().getPosition()).getMeters(); |
|
| 73 | + final double meters = nextFixPair.getA().getPosition().getDistance(nextFixPair.getB().getPosition()).getMeters(); |
|
| 74 | 74 | if (meters <= MAX_DISTANCE_METERS && meters >= MIN_DISTANCE_METERS) { |
| 75 | - double twdChange = nextFixPair.getA().getBearing().getDifferenceTo(nextFixPair.getB().getBearing()).getDegrees(); |
|
| 76 | - SingleDimensionBasedTwdTransition entry = new SingleDimensionBasedTwdTransition(meters, twdChange); |
|
| 75 | + double twdChangeInDegrees = nextFixPair.getA().getBearing().getDifferenceTo(nextFixPair.getB().getBearing()).getDegrees(); |
|
| 76 | + SingleDimensionBasedTwdTransition entry = new SingleDimensionBasedTwdTransition(meters, twdChangeInDegrees); |
|
| 77 | 77 | distanceBasedTwdTransitionPersistenceManager.add(entry); |
| 78 | 78 | totalValuesCount++; |
| 79 | 79 | } |
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDistanceBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DistanceBasedTwdTransitionRegressorFrom0.0To10.0.clf
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDistanceBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DistanceBasedTwdTransitionRegressorFrom10.0To912.0.clf
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDistanceBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DistanceBasedTwdTransitionRegressorFrom1368.0ToMaximum.clf
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDistanceBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DistanceBasedTwdTransitionRegressorFrom912.0To1368.0.clf
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDurationBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DurationBasedTwdTransitionRegressorFrom0.0To1.0.clf
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDurationBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DurationBasedTwdTransitionRegressorFrom1.0To140.0.clf
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDurationBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DurationBasedTwdTransitionRegressorFrom140.0To5394.0.clf
java/com.sap.sailing.windestimation.test/resources/trained_wind_estimation_models/SERIALIZATION.modelForDurationBasedTwdDeltaStdRegressor.IncrementalSingleDimensionPolynomialRegressor.DurationBasedTwdTransitionRegressorFrom5394.0ToMaximum.clf
java/com.sap.sailing.xrr.resultimport.test/src/com/sap/sailing/xrr/resultimport/test/ParserTest.java
| ... | ... | @@ -10,6 +10,7 @@ import java.io.FileInputStream; |
| 10 | 10 | import java.io.FileNotFoundException; |
| 11 | 11 | import java.io.IOException; |
| 12 | 12 | import java.io.InputStream; |
| 13 | +import java.net.URISyntaxException; |
|
| 13 | 14 | import java.util.ArrayList; |
| 14 | 15 | import java.util.Arrays; |
| 15 | 16 | import java.util.Calendar; |
| ... | ... | @@ -87,7 +88,7 @@ public class ParserTest { |
| 87 | 88 | |
| 88 | 89 | @Test |
| 89 | 90 | public void testScoreCorrectionProviderFeedingAndHasResults() throws IOException, SAXException, |
| 90 | - ParserConfigurationException, JAXBException { |
|
| 91 | + ParserConfigurationException, JAXBException, URISyntaxException { |
|
| 91 | 92 | ScoreCorrectionProviderImpl scoreCorrectionProvider = new ScoreCorrectionProviderImpl(getTestDocumentProvider(), |
| 92 | 93 | ParserFactory.INSTANCE); |
| 93 | 94 | Map<String, Set<com.sap.sse.common.Util.Pair<String, TimePoint>>> hasResultsFor = scoreCorrectionProvider.getHasResultsForBoatClassFromDateByEventName(); |
| ... | ... | @@ -103,7 +104,7 @@ public class ParserTest { |
| 103 | 104 | |
| 104 | 105 | @Test |
| 105 | 106 | public void testScoreCorrectionProvider() throws IOException, SAXException, |
| 106 | - ParserConfigurationException, JAXBException { |
|
| 107 | + ParserConfigurationException, JAXBException, URISyntaxException { |
|
| 107 | 108 | ScoreCorrectionProviderImpl scoreCorrectionProvider = new ScoreCorrectionProviderImpl(getTestDocumentProvider(), |
| 108 | 109 | ParserFactory.INSTANCE); |
| 109 | 110 | Map<String, Set<com.sap.sse.common.Util.Pair<String, TimePoint>>> hasResultsFor = scoreCorrectionProvider.getHasResultsForBoatClassFromDateByEventName(); |
| ... | ... | @@ -140,7 +141,7 @@ public class ParserTest { |
| 140 | 141 | |
| 141 | 142 | @Test |
| 142 | 143 | public void testScoreCorrectionProviderForSplitFleet() throws IOException, SAXException, |
| 143 | - ParserConfigurationException, JAXBException { |
|
| 144 | + ParserConfigurationException, JAXBException, URISyntaxException { |
|
| 144 | 145 | ScoreCorrectionProviderImpl scoreCorrectionProvider = new ScoreCorrectionProviderImpl(getTestDocumentProvider(), |
| 145 | 146 | ParserFactory.INSTANCE); |
| 146 | 147 | Map<String, Set<com.sap.sse.common.Util.Pair<String, TimePoint>>> hasResultsFor = scoreCorrectionProvider.getHasResultsForBoatClassFromDateByEventName(); |
java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/CompetitorResolver.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.xrr.resultimport.impl; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | 4 | import java.io.Serializable; |
| 5 | +import java.net.URISyntaxException; |
|
| 5 | 6 | import java.util.ArrayList; |
| 6 | 7 | import java.util.HashMap; |
| 7 | 8 | import java.util.HashSet; |
| ... | ... | @@ -58,9 +59,8 @@ public class CompetitorResolver { |
| 58 | 59 | * information about the results.. |
| 59 | 60 | * |
| 60 | 61 | * @return A Map, with sets of Regatta names as values, keyed by the events they belong to |
| 61 | - * @throws IOException |
|
| 62 | 62 | */ |
| 63 | - public Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException { |
|
| 63 | + public Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException, URISyntaxException { |
|
| 64 | 64 | Map<String, Set<String>> result = new HashMap<>(); |
| 65 | 65 | for (ResultDocumentDescriptor resultDocDescr : documentProvider.getResultDocumentDescriptors()) { |
| 66 | 66 | final String eventName = resultDocDescr.getEventName(); |
| ... | ... | @@ -83,11 +83,9 @@ public class CompetitorResolver { |
| 83 | 83 | * @param optional |
| 84 | 84 | * regattaName |
| 85 | 85 | * @return A List of CompetitorDescriptors competing in a specified Event and Regatta |
| 86 | - * @throws JAXBException |
|
| 87 | - * @throws IOException |
|
| 88 | 86 | */ |
| 89 | 87 | public Iterable<CompetitorDescriptor> getCompetitorDescriptors(String eventName, String regattaName) |
| 90 | - throws JAXBException, IOException { |
|
| 88 | + throws JAXBException, IOException, URISyntaxException { |
|
| 91 | 89 | final List<CompetitorDescriptor> result = new ArrayList<>(); |
| 92 | 90 | final Map<String, CompetitorDescriptor> resultsByTeamID = new HashMap<>(); |
| 93 | 91 | final Map<String, CompetitorDescriptor> teamsWithoutRaceAssignments = new HashMap<>(); // keys are the teamID |
java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/ScoreCorrectionProviderImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.xrr.resultimport.impl; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | +import java.net.URISyntaxException; |
|
| 5 | 6 | import java.util.ArrayList; |
| 6 | 7 | import java.util.HashMap; |
| 7 | 8 | import java.util.HashSet; |
| ... | ... | @@ -48,7 +49,7 @@ public class ScoreCorrectionProviderImpl extends AbstractDocumentBasedScoreCorre |
| 48 | 49 | |
| 49 | 50 | @Override |
| 50 | 51 | public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() |
| 51 | - throws IOException, SAXException, ParserConfigurationException { |
|
| 52 | + throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 52 | 53 | Map<String, Set<Util.Pair<String, TimePoint>>> result = new HashMap<String, Set<Util.Pair<String, TimePoint>>>(); |
| 53 | 54 | for (Parser parser : getAllRegattaResults()) { |
| 54 | 55 | try { |
| ... | ... | @@ -81,7 +82,7 @@ public class ScoreCorrectionProviderImpl extends AbstractDocumentBasedScoreCorre |
| 81 | 82 | |
| 82 | 83 | @Override |
| 83 | 84 | public RegattaScoreCorrections getScoreCorrections(String eventName, String boatClassName, |
| 84 | - TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException { |
|
| 85 | + TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 85 | 86 | for (Parser parser : getAllRegattaResults()) { |
| 86 | 87 | try { |
| 87 | 88 | final RegattaResults regattaResults = parser.parse(); |
| ... | ... | @@ -108,7 +109,7 @@ public class ScoreCorrectionProviderImpl extends AbstractDocumentBasedScoreCorre |
| 108 | 109 | } |
| 109 | 110 | |
| 110 | 111 | private Iterable<Parser> getAllRegattaResults() throws SAXException, IOException, |
| 111 | - ParserConfigurationException { |
|
| 112 | + ParserConfigurationException, URISyntaxException { |
|
| 112 | 113 | final List<Parser> result = new ArrayList<>(); |
| 113 | 114 | for (ResultDocumentDescriptor resultDocDescr : getResultDocumentProvider().getResultDocumentDescriptors()) { |
| 114 | 115 | final Parser parser = parserFactory.createParser(resultDocDescr.getInputStream(), resultDocDescr.getDocumentName()); |
java/com.sap.sailing.xrr.structureimport/src/com/sapsailing/xrr/structureimport/eventimport/EventImport.java
| ... | ... | @@ -1,8 +1,7 @@ |
| 1 | 1 | package com.sapsailing.xrr.structureimport.eventimport; |
| 2 | 2 | |
| 3 | 3 | public class EventImport { |
| 4 | - |
|
| 5 | - public Iterable<RegattaJSON> getRegattas(String url){ |
|
| 4 | + public Iterable<RegattaJSON> getRegattas(String url) { |
|
| 6 | 5 | EventResults results = new EventParser().parseEvent(url); |
| 7 | 6 | return results.getRegattas(); |
| 8 | 7 | } |
java/com.sap.sailing.xrr.structureimport/src/com/sapsailing/xrr/structureimport/eventimport/EventParser.java
| ... | ... | @@ -24,7 +24,6 @@ public class EventParser { |
| 24 | 24 | JSONObject jsonRoot; |
| 25 | 25 | try { |
| 26 | 26 | InputStreamReader streamReader = getStreamReader(url); |
| 27 | - |
|
| 28 | 27 | jsonRoot = (JSONObject) new JSONParser().parse(streamReader); |
| 29 | 28 | String id = (String) jsonRoot.get("Id"); |
| 30 | 29 | String name = (String) jsonRoot.get("Name"); |
java/com.sap.sailing.yachtscoring.resultimport.test/src/com/sap/sailing/yachtscoring/resultimport/test/CharlstonRaceWeek2015_ParserTest.java
| ... | ... | @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull; |
| 5 | 5 | import static org.mockito.Mockito.mock; |
| 6 | 6 | |
| 7 | 7 | import java.io.IOException; |
| 8 | +import java.net.URISyntaxException; |
|
| 8 | 9 | import java.util.Map; |
| 9 | 10 | import java.util.Set; |
| 10 | 11 | |
| ... | ... | @@ -35,16 +36,14 @@ public class CharlstonRaceWeek2015_ParserTest extends AbstractCharlstonRaceWeek2 |
| 35 | 36 | |
| 36 | 37 | @Test |
| 37 | 38 | public void testScoreCorrectionProviderFeedingAndHasResults() throws IOException, SAXException, |
| 38 | - ParserConfigurationException, JAXBException { |
|
| 39 | + ParserConfigurationException, JAXBException, URISyntaxException { |
|
| 39 | 40 | ResultUrlRegistry resultUrlRegistry = new ResultUrlRegistryImpl(mock(MongoObjectFactory.class), |
| 40 | 41 | mock(DomainObjectFactory.class)); |
| 41 | 42 | ScoreCorrectionProviderImpl scoreCorrectionProvider = new ScoreCorrectionProviderImpl( |
| 42 | 43 | getTestDocumentProvider(), ParserFactory.INSTANCE, resultUrlRegistry); |
| 43 | 44 | Map<String, Set<com.sap.sse.common.Util.Pair<String, TimePoint>>> hasResultsFor = scoreCorrectionProvider.getHasResultsForBoatClassFromDateByEventName(); |
| 44 | - |
|
| 45 | 45 | Set<com.sap.sse.common.Util.Pair<String, TimePoint>> resultsForKeyWestRaceWeek = hasResultsFor.get(CHARLSTONRACEWEEK2015_EVENT_NAME); |
| 46 | 46 | assertNotNull(resultsForKeyWestRaceWeek); |
| 47 | - |
|
| 48 | 47 | assertEquals(2, resultsForKeyWestRaceWeek.size()); |
| 49 | 48 | } |
| 50 | 49 |
java/com.sap.sailing.yachtscoring.resultimport.test/src/com/sap/sailing/yachtscoring/resultimport/test/CompetitorImportTest.java
| ... | ... | @@ -8,6 +8,7 @@ import static org.mockito.Mockito.when; |
| 8 | 8 | |
| 9 | 9 | import java.io.FileNotFoundException; |
| 10 | 10 | import java.io.IOException; |
| 11 | +import java.net.URISyntaxException; |
|
| 11 | 12 | import java.util.Arrays; |
| 12 | 13 | |
| 13 | 14 | import javax.xml.bind.JAXBException; |
| ... | ... | @@ -23,7 +24,7 @@ import com.sap.sse.common.Util; |
| 23 | 24 | |
| 24 | 25 | public class CompetitorImportTest extends AbstractCharlstonRaceWeek2015Test { |
| 25 | 26 | @Test |
| 26 | - public void simpleCompetitorImportTest() throws FileNotFoundException, IOException, JAXBException { |
|
| 27 | + public void simpleCompetitorImportTest() throws FileNotFoundException, IOException, JAXBException, URISyntaxException { |
|
| 27 | 28 | ResultUrlRegistry resultUrlRegistry = mock(ResultUrlRegistry.class); |
| 28 | 29 | when(resultUrlRegistry.getReadableResultUrls(AbstractYachtScoringProvider.NAME)).thenReturn(Arrays.asList(getClass().getClassLoader().getResource(CHARLSTONRACEWEEK2015_TESTFILE_XRR))); |
| 29 | 30 | final YachtScoringCompetitorProvider competitorProvider = new YachtScoringCompetitorProvider(ParserFactory.INSTANCE, resultUrlRegistry, getTestDocumentProvider()); |
java/com.sap.sailing.yachtscoring.resultimport.test/src/com/sap/sailing/yachtscoring/resultimport/test/KeyWestRaceWeek2016_ParserTest.java
| ... | ... | @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull; |
| 5 | 5 | import static org.mockito.Mockito.mock; |
| 6 | 6 | |
| 7 | 7 | import java.io.IOException; |
| 8 | +import java.net.URISyntaxException; |
|
| 8 | 9 | import java.util.ArrayList; |
| 9 | 10 | import java.util.Date; |
| 10 | 11 | import java.util.List; |
| ... | ... | @@ -72,16 +73,14 @@ public class KeyWestRaceWeek2016_ParserTest extends AbstractYachtScoringEventRes |
| 72 | 73 | |
| 73 | 74 | @Test |
| 74 | 75 | public void testScoreCorrectionProviderFeedingAndHasResults() throws IOException, SAXException, |
| 75 | - ParserConfigurationException, JAXBException { |
|
| 76 | + ParserConfigurationException, JAXBException, URISyntaxException { |
|
| 76 | 77 | ResultUrlRegistry resultUrlRegistry = new ResultUrlRegistryImpl(mock(MongoObjectFactory.class), |
| 77 | 78 | mock(DomainObjectFactory.class)); |
| 78 | 79 | ScoreCorrectionProviderImpl scoreCorrectionProvider = new ScoreCorrectionProviderImpl( |
| 79 | 80 | getTestDocumentProvider(), ParserFactory.INSTANCE, resultUrlRegistry); |
| 80 | 81 | Map<String, Set<com.sap.sse.common.Util.Pair<String, TimePoint>>> hasResultsFor = scoreCorrectionProvider.getHasResultsForBoatClassFromDateByEventName(); |
| 81 | - |
|
| 82 | 82 | Set<com.sap.sse.common.Util.Pair<String, TimePoint>> resultsForKeyWestRaceWeek = hasResultsFor.get(KEYWESTRACEWEEEK2016_EVENT_NAME); |
| 83 | 83 | assertNotNull(resultsForKeyWestRaceWeek); |
| 84 | - |
|
| 85 | 84 | assertEquals(2, resultsForKeyWestRaceWeek.size()); |
| 86 | 85 | } |
| 87 | 86 | |
| ... | ... | @@ -100,7 +99,6 @@ public class KeyWestRaceWeek2016_ParserTest extends AbstractYachtScoringEventRes |
| 100 | 99 | } |
| 101 | 100 | } |
| 102 | 101 | assertNotNull(resultForJ88); |
| 103 | - |
|
| 104 | 102 | RegattaScoreCorrections _J88Result = scoreCorrectionProvider.getScoreCorrections(KEYWESTRACEWEEEK2016_EVENT_NAME, BOAT_CLASS_J88, |
| 105 | 103 | resultForJ88.getB()); |
| 106 | 104 | assertNotNull(_J88Result); |
| ... | ... | @@ -108,5 +106,4 @@ public class KeyWestRaceWeek2016_ParserTest extends AbstractYachtScoringEventRes |
| 108 | 106 | assertNotNull(scoreCorrectionsForRaces); |
| 109 | 107 | assertEquals(10, Util.size(scoreCorrectionsForRaces)); |
| 110 | 108 | } |
| 111 | - |
|
| 112 | 109 | } |
java/com.sap.sailing.yachtscoring.resultimport/src/com/sap/sailing/yachtscoring/resultimport/ScoreCorrectionProviderImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.yachtscoring.resultimport; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | 4 | import java.io.InputStream; |
| 5 | +import java.net.URISyntaxException; |
|
| 5 | 6 | import java.util.HashMap; |
| 6 | 7 | import java.util.HashSet; |
| 7 | 8 | import java.util.Map; |
| ... | ... | @@ -42,7 +43,7 @@ public class ScoreCorrectionProviderImpl extends AbstractYachtScoringProvider im |
| 42 | 43 | } |
| 43 | 44 | |
| 44 | 45 | @Override |
| 45 | - public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() { |
|
| 46 | + public Map<String, Set<Util.Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() throws URISyntaxException { |
|
| 46 | 47 | Map<String, Set<Util.Pair<String, TimePoint>>> result = new HashMap<String, Set<Util.Pair<String,TimePoint>>>(); |
| 47 | 48 | try { |
| 48 | 49 | for (ResultDocumentDescriptor resultDocDescr : documentProvider.getResultDocumentDescriptors()) { |
| ... | ... | @@ -68,7 +69,7 @@ public class ScoreCorrectionProviderImpl extends AbstractYachtScoringProvider im |
| 68 | 69 | |
| 69 | 70 | @Override |
| 70 | 71 | public RegattaScoreCorrections getScoreCorrections(String eventName, String boatClassName, |
| 71 | - TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException { |
|
| 72 | + TimePoint timePointPublished) throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
|
| 72 | 73 | Parser parser = resolveParser(eventName, boatClassName); |
| 73 | 74 | try { |
| 74 | 75 | RegattaResults regattaResults = parser.parse(); |
| ... | ... | @@ -89,7 +90,7 @@ public class ScoreCorrectionProviderImpl extends AbstractYachtScoringProvider im |
| 89 | 90 | /* boatClassNameFilter */ Optional.empty()); |
| 90 | 91 | } |
| 91 | 92 | |
| 92 | - private Parser resolveParser(String eventName, String boatClassName) throws IOException { |
|
| 93 | + private Parser resolveParser(String eventName, String boatClassName) throws IOException, URISyntaxException { |
|
| 93 | 94 | Parser result = null; |
| 94 | 95 | for (ResultDocumentDescriptor resultDocDescr : documentProvider.getResultDocumentDescriptors()) { |
| 95 | 96 | if (eventName.equals(resultDocDescr.getEventName()) && boatClassName.equals(resultDocDescr.getBoatClass())) { |
java/com.sap.sailing.yachtscoring.resultimport/src/com/sap/sailing/yachtscoring/resultimport/YachtScoringCompetitorProvider.java
| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | package com.sap.sailing.yachtscoring.resultimport; |
| 2 | 2 | |
| 3 | 3 | import java.io.IOException; |
| 4 | +import java.net.URISyntaxException; |
|
| 4 | 5 | import java.util.Locale; |
| 5 | 6 | import java.util.Map; |
| 6 | 7 | import java.util.Set; |
| ... | ... | @@ -40,12 +41,12 @@ public class YachtScoringCompetitorProvider extends AbstractYachtScoringProvider |
| 40 | 41 | } |
| 41 | 42 | |
| 42 | 43 | @Override |
| 43 | - public Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException { |
|
| 44 | + public Map<String, Set<String>> getHasCompetitorsForRegattasInEvent() throws IOException, URISyntaxException { |
|
| 44 | 45 | return competitorResolver.getHasCompetitorsForRegattasInEvent(); |
| 45 | 46 | } |
| 46 | 47 | |
| 47 | 48 | @Override |
| 48 | - public Iterable<CompetitorDescriptor> getCompetitorDescriptors(String eventName, String regattaName) throws JAXBException, IOException { |
|
| 49 | + public Iterable<CompetitorDescriptor> getCompetitorDescriptors(String eventName, String regattaName) throws JAXBException, IOException, URISyntaxException { |
|
| 49 | 50 | return competitorResolver.getCompetitorDescriptors(eventName, regattaName); |
| 50 | 51 | } |
| 51 | 52 |
java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties
| ... | ... | @@ -6,7 +6,7 @@ regattaName = Regatta Name |
| 6 | 6 | crossSum = Quersumme |
| 7 | 7 | Name=Name |
| 8 | 8 | Year = Jahr |
| 9 | -LegNumber=Anzahl der Schenkel |
|
| 9 | +LegNumber=Anzahl der Bahnschenkel |
|
| 10 | 10 | SailID=Segelnummer |
| 11 | 11 | TestCluster=Test Cluster Deutsch |
| 12 | 12 | increment=Inkrementieren |
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DOMUtils.java
| ... | ... | @@ -2,6 +2,10 @@ package com.sap.sse.gwt.client; |
| 2 | 2 | |
| 3 | 3 | import com.google.gwt.dom.client.Document; |
| 4 | 4 | import com.google.gwt.dom.client.Element; |
| 5 | +import com.google.gwt.dom.client.NodeList; |
|
| 6 | +import com.google.gwt.dom.client.OptionElement; |
|
| 7 | +import com.google.gwt.dom.client.SelectElement; |
|
| 8 | +import com.google.gwt.user.client.ui.ListBox; |
|
| 5 | 9 | import com.google.gwt.user.client.ui.Widget; |
| 6 | 10 | |
| 7 | 11 | /** |
| ... | ... | @@ -37,4 +41,13 @@ public final class DOMUtils { |
| 37 | 41 | return doc.isCSS1Compat() ? doc.getDocumentElement() : doc.getBody(); |
| 38 | 42 | } |
| 39 | 43 | |
| 44 | + /** |
|
| 45 | + * Returns a live list of the {@link OptionElement}s constituting the list box contents. Live here means that |
|
| 46 | + * adding/removing items to the list box reflects in the node list returned. The {@link OptionElement}s in the node |
|
| 47 | + * list can be used, e.g., to set a tooltip on the elements, using {@link OptionElement#setTitle(String)}. |
|
| 48 | + */ |
|
| 49 | + public static NodeList<OptionElement> getOptions(ListBox listBox) { |
|
| 50 | + final SelectElement selectElement = listBox.getElement().cast(); |
|
| 51 | + return selectElement.getOptions(); |
|
| 52 | + } |
|
| 40 | 53 | } |
| ... | ... | \ No newline at end of file |
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java
| ... | ... | @@ -15,12 +15,12 @@ import com.sap.sse.common.Util.Pair; |
| 15 | 15 | * An Executor for efficient, asynchronous execution of {@link TimeRangeAsyncAction}s. This executor operates on |
| 16 | 16 | * {@link TimeRangeAsyncAction}s which specify the wanted {@link TimeRange} by {@link Key} with |
| 17 | 17 | * {@link TimeRangeAsyncAction#getTimeRanges()} and are expected to return a {@code Result} when their |
| 18 | - * {@link TimeRangeAsyncAction#execute(Map<Key,TimeRange>, AsyncCallback)} is invoked. The {@link Key} could, e.g., be a |
|
| 18 | + * {@link TimeRangeAsyncAction#execute(Map, AsyncCallback)} is invoked. The {@link Key} could, e.g., be a |
|
| 19 | 19 | * competitor or a boat class. |
| 20 | 20 | * <p> |
| 21 | 21 | * |
| 22 | 22 | * This executor manages and optimizes concurrent requests for equal {@code Key}s that have overlapping time ranges. |
| 23 | - * This way, redundant requests for the same data can be avoided. This executor collects the results of the overlapping |
|
| 23 | + * This way, redundant requests for the same data can thus be avoided. This executor collects the results of the overlapping |
|
| 24 | 24 | * concurrent requests and uses them to satisfy each individual client request as if they had all been executed in their |
| 25 | 25 | * entirety. |
| 26 | 26 | * <p> |
java/com.sap.sse.security.datamining/src/com/sap/sse/security/datamining/components/SecuritySessionsRetrievalProcessor.java
| ... | ... | @@ -43,7 +43,7 @@ public class SecuritySessionsRetrievalProcessor extends AbstractRetrievalProcess |
| 43 | 43 | final PrincipalCollection pc = (PrincipalCollection) attributeValue; |
| 44 | 44 | if (pc != null && pc.getPrimaryPrincipal() != null) { |
| 45 | 45 | final User user = securityService.getUserByName((String) pc.getPrimaryPrincipal()); |
| 46 | - if (user != null) { |
|
| 46 | + if (user != null && securityService.hasCurrentUserReadPermission(user)) { |
|
| 47 | 47 | data.add(new SessionWithContext(securityService, session, user)); |
| 48 | 48 | break; |
| 49 | 49 | } |
java/target/env-default-rules.sh
| ... | ... | @@ -107,3 +107,12 @@ fi |
| 107 | 107 | if [ -n ${CHARGEBEE_SITE} -a -n ${CHARGEBEE_APIKEY} ]; then |
| 108 | 108 | ADDITIONAL_JAVA_ARGS="${ADDITIONAL_JAVA_ARGS} -Dchargebee.site=${CHARGEBEE_SITE} -Dchargebee.apikey=${CHARGEBEE_APIKEY}" |
| 109 | 109 | fi |
| 110 | +if [ -n ${MANAGE2SAIL_ACCESS_TOKEN} ]; then |
|
| 111 | + ADDITIONAL_JAVA_ARGS="${ADDITIONAL_JAVA_ARGS} -Dmanage2sail.accesstoken=${MANAGE2SAIL_ACCESS_TOKEN}" |
|
| 112 | +fi |
|
| 113 | +if [ -n ${IGTIMI_CLIENT_ID} ]; then |
|
| 114 | + ADDITIONAL_JAVA_ARGS="${ADDITIONAL_JAVA_ARGS} -Digtimi.client.id=${IGTIMI_CLIENT_ID}" |
|
| 115 | +fi |
|
| 116 | +if [ -n ${IGTIMI_CLIENT_SECRET} ]; then |
|
| 117 | + ADDITIONAL_JAVA_ARGS="${ADDITIONAL_JAVA_ARGS} -Digtimi.client.secret=${IGTIMI_CLIENT_SECRET}" |
|
| 118 | +fi |
wiki/howto/development/gwt-ui-binder.md
| ... | ... | @@ -30,13 +30,13 @@ Ubilabs will deliver two versions of the site: one for use on desktops and table |
| 30 | 30 | |
| 31 | 31 | ## Internationalization (i18n) |
| 32 | 32 | |
| 33 | -We're not yet decided on how to implement i18n. GWT offers two mechanisms that seem viable, see http://www.gwtproject.org/doc/latest/DevGuideUiBinderI18n.html. We could use GWT's feature that generates the property files. The upside would be that in case of many static texts we would get a quick start. The generation of UUIDs from the original static texts additionally ensures that changes cause compile errors and hence notify us that re-translation is required, which is good. |
|
| 33 | +GWT offers two mechanisms for i18n, see [https://www.gwtproject.org/doc/latest/DevGuideUiBinderI18n.html](https://www.gwtproject.org/doc/latest/DevGuideUiBinderI18n.html) for the UI Binder approach and [https://www.gwtproject.org/doc/latest/DevGuideI18nMessages.html](https://www.gwtproject.org/doc/latest/DevGuideI18nMessages.html) for explicit message declaration. The upside of GWT's feature that generates the property files would be that in case of many static texts we would get a quick start. The generation of UUIDs from the original static texts additionally ensures that changes cause compile errors and hence notify us that re-translation is required, which is good. |
|
| 34 | 34 | |
| 35 | 35 | The downside is that redundancies across different entry points are not handled well and that the property files may not fit in well with programmatic use of those message strings. |
| 36 | 36 | |
| 37 | -Alternatively, it is possible to declare the string messages class and the method name that shall be used for the string message property. This way, we could consolidate all texts into a single message properties file which may help avoid redundancies. |
|
| 37 | +Alternatively, it is possible to declare the string messages class and the method name that shall be used for the string message property. This way, we consolidate all texts into a single message properties file which may help avoid redundancies. This is what we have decided for. Find those various ``StringMessages.java`` and corresponding ``StringMessages.properties`` in various places. |
|
| 38 | 38 | |
| 39 | -We'll probably try out both approaches in a small scale and then decide. |
|
| 39 | +More details on the i18n process, e.g., how translation is triggered, can be found [here](/wiki/howto/development/i18n). |
|
| 40 | 40 | |
| 41 | 41 | ## UI Architecture |
| 42 | 42 |
wiki/howto/development/i18n.md
| ... | ... | @@ -0,0 +1,113 @@ |
| 1 | +# Internationalization (i18n) |
|
| 2 | + |
|
| 3 | +The topic of internationalization deals largely with the translation of UI texts into different languages / locales. In this project we see three different approaches: |
|
| 4 | + |
|
| 5 | +- message strings for web user interfaces built with the Google Web Toolkit (GWT), with i18n happening on the client based, e.g., on the browser's locale |
|
| 6 | +- message strings display in the web user interface, but with the locale and message selection happening on the server side, e.g., for the Data Mining framework |
|
| 7 | +- message strings for mobile apps |
|
| 8 | + |
|
| 9 | +For all these cases, English (en) and German (de) are the development locales where development maintains the message strings. All other languages (at the time of this writing these are zh, ru, ja, es, fr, pt, it, da, sl, and cs) will be handled by SAP Language Labs (LXLabs). The process for this is explained below. |
|
| 10 | + |
|
| 11 | +## GWT UI, Messages |
|
| 12 | + |
|
| 13 | +[https://www.gwtproject.org/doc/latest/DevGuideI18nMessages.html](https://www.gwtproject.org/doc/latest/DevGuideI18nMessages.html) documents the basics. We have several such messages interfaces, such as ``com.sap.sailing.gwt.ui.client.StringMessages`` in the ``com.sap.sailing.gwt.ui`` bundle. These message files contain key/value pairs where the values may contain placeholders for parameters which have to match the corresponding method signature's parameters. The message files use the general Java properties file syntax, in particular the single-quote magic for escaping character sequences which otherwise would have special meaning. Placeholders are written as the zero-based parameter number in curly braces (``{0}``, ``{1}``, etc.). |
|
| 14 | + |
|
| 15 | +For accessing these messages in client-side GWT code, obtain an instance of the messages interface, as so: |
|
| 16 | + |
|
| 17 | +``` |
|
| 18 | + public static final StringMessages INSTANCE = GWT.create(StringMessages.class); |
|
| 19 | +``` |
|
| 20 | + |
|
| 21 | +## Backend Messages |
|
| 22 | + |
|
| 23 | +When message strings have to be translated based on a locale already in the back-end, ``com.sap.sse.i18n.ResourceBundleStringMessages`` can be used. Create one instance like this: |
|
| 24 | + |
|
| 25 | +``` |
|
| 26 | + private static final String STRING_MESSAGES_BASE_NAME = "stringmessages/Polars_StringMessages"; |
|
| 27 | + |
|
| 28 | + sailingServerStringMessages = new ResourceBundleStringMessagesImpl( |
|
| 29 | + STRING_MESSAGES_BASE_NAME, getClassLoader(), |
|
| 30 | + StandardCharsets.UTF_8.name()); |
|
| 31 | +``` |
|
| 32 | + |
|
| 33 | +The same quoting, escaping and placeholder rules apply as for standard Java properties files and hence the GWT UI messages files. The difference, when compared to the GWT UI messages, is that there is no Java interface type that models each message as a method. Instead, the default "en" locale is then provided by a file named like the base name provided to the constructor, with the ``.properties`` suffix appended. A good place is a ``resources/`` source folder in your bundle which then contains a ``stringmessages`` sub-folder in which the ``.properties`` files reside. In the example above, the files would have paths relative to the bundle's root folder like this: ``resources/stringmessages/Polars_StringMessages.properties`` for the default "en" locale, or ``resources/stringmessages/Polars_StringMessages_es.properties`` for the Spanish locale. |
|
| 34 | + |
|
| 35 | +The string message values are obtained in the back-end code ``ResourceBundleStringMessages.get`` methods, passing the user's locale that may, e.g., be inferred through ``ProxiedRemoteServiceServlet.getClientLocale()`` when in a GWT RPC service implementation. |
|
| 36 | + |
|
| 37 | +## Mobile Apps |
|
| 38 | + |
|
| 39 | +We can distinguish three types of mobile apps that were created so far over the course of the program. We started by implementing a first native Android app (SAP Sailing Race Manager), using Java as the development language, sharing code also with the back-end logic. Two more apps followed with the same approach: the SAP Sailing Buoy Pinger and the self-tracking SAP Sail InSight app. |
|
| 40 | + |
|
| 41 | +SAP Sail InSight was then also ported to iOS, using Swift / ObjectiveC as languages. |
|
| 42 | + |
|
| 43 | +Later, SAP Sail InSight was developed again from the ground up, this time as a React Native app using TypeScript as the development language. Its naming was slightly altered into "Sail Insight (Powered by SAP)", and it went through different organizations taking care of and publishing it. |
|
| 44 | + |
|
| 45 | +All these approaches differ in how i18n is done for them. |
|
| 46 | + |
|
| 47 | +### Native Android Apps |
|
| 48 | + |
|
| 49 | +These keep their message strings in files called ``strings.xml`` under the ``res/`` folder in the respective app's root folder. They are located in directories named after the locale for which they are used. The default "en" locale uses ``values`` as the directory name, so in total ``mobile/{app-root-folder}/res/values/strings.xml``. Other locales append the locale name to the ``values`` folder name, separated by a dash. Example: ``mobile/{app-root-folder}/res/values-pt/strings.xml`` for the Portugese translations. |
|
| 50 | + |
|
| 51 | +### Old iOS App |
|
| 52 | + |
|
| 53 | +The old iOS version of SAP Sail InSight is still around in our repository. It lives under the ``ios`` root folder in our Git workspace. For each locale there is a ``*.lproj`` sub-folder in ``ios/SAPTracker/src/xcode/SAPTracker`` where the default "en" locale is represented by the ``Base.lproj`` folder, Chinese would then be ``ios/SAPTracker/src/xcode/SAPTracker/zh.lproj``, and so on. In each such ``*.lproj`` folder there are two files: ``InfoPlist.strings`` and ``Localizable.strings`` out of which the app draws the message strings to display for the user. |
|
| 54 | + |
|
| 55 | +### React Native Version of Sail Insight |
|
| 56 | + |
|
| 57 | +The primary repository for the app lives at ``trac@sapsailing.com:/home/trac/sailinsight-git``. A copy exists on Github: ``git@github.com:SailTracks/sailinsight.git``. In order to have these be subject to translation within our existing project with SAP LXLabs, we create a synchronization job that copies the message strings from the ``*.json`` files in the ``src/i18n/translations`` directory of the app repository into the equal-named folder of our default SAP Sailing Analytics repository (currently ``ssh://trac@sapsailing.com/home/trac/git``). When translation is completed, translation results need to be transferred back into the Sail Insight repository. |
|
| 58 | + |
|
| 59 | +## Triggering and Integrating Translations |
|
| 60 | + |
|
| 61 | +We entertain a Git repo clone at ``https://github.tools.sap/SAP-Sailing-Analytics/sapsailing``. When we push new commits to the ``translation`` branch of that repository (see [https://github.tools.sap/SAP-Sailing-Analytics/sapsailing/tree/translation](https://github.tools.sap/SAP-Sailing-Analytics/sapsailing/tree/translation)) then LXLabs will grab these changes in regular intervals (as of this writing typically every Friday evening) and look for changes in files that belong to any message collection. |
|
| 62 | + |
|
| 63 | +Message collections are declared in the file ``translation_v2.json`` in the root of our Git workspace. Adding new collections to this file requires us to inform LXLabs (e.g., vyacheslav.sklyarenko@sap.com, called "Slava"). They need to make the corresponding update in the so-called [Translation Enablement Workbench](https://tew.ingress.prod.lp.shoot.live.k8s-hana.ondemand.com/#/param/WzIwMiw1NiwiLyIsInRhc2tRdWV1ZSJd). |
|
| 64 | + |
|
| 65 | +During the translation process, translators may have questions regarding new or changed message strings. This will results in tasks being assigned by the Translation Enablement Workbench, and an e-mail to the assignee will result. Currently, the default assignee for these tasks is axel.uhl@sap.com. We then need to answer these questions as soon as possible to unblock the translation process so that ideally we get the translation in the same sweep as those for other languages, in the same round. |
|
| 66 | + |
|
| 67 | +When translations are done, they are pushed to the same ``translation`` branch of ``https://github.tools.sap/SAP-Sailing-Analytics/sapsailing`` by LXLabs and then usually contain the translation of all changed or added message strings. LXLabs goes by the default locale ("en") and ignores all we do in the German ("de") locale. |
|
| 68 | + |
|
| 69 | +We have a Github webhook installed (see [https://github.tools.sap/SAP-Sailing-Analytics/sapsailing/settings/hooks/285417](https://github.tools.sap/SAP-Sailing-Analytics/sapsailing/settings/hooks/285417)) which triggers a CGI-BIN script that is installed on sapsailing.com at ``/var/www/cgi-bin/github.cgi`` and is versioned under ``configuration/httpd/cgi-bin/github.cgi`` to which ``/var/www/cgi-bin/github.cgi`` is a symbolic link, and which reacts to pushes from the user with the e-mail address ``tmsatsls+github.tools.sap_service-tip-git@sap.com``, pushing to branch ``translation``. In this case, the Git workspace installed at ``/home/wiki/gitwiki`` for the user ``wiki`` is used to fetch the latest translations, using the Git remote ``sapsailing`` which is expected to use a Personal Access Token (PAT) that is granted read access to the repository. The changes are then pushed to the ``translation`` branch on ``ssh://trac@sapsailing.com/home/trac/git`` which is expected to trigger the [hudson job for the translation branch](https://hudson.sapsailing.com/job/SAPSailingAnalytics-translation/). This, in turn, will run a full build, and if successful, merge the ``translation`` branch into ``master`` and push it back to ``ssh://trac@sapsailing.com/home/trac/git`` which will then trigger the [master's build job](https://hudson.sapsailing.com/job/SAPSailingAnalytics-master/). |
|
| 70 | + |
|
| 71 | +First building in a separate job before merging into master partly goes back to the times when LXLabs had issues exporting Java properties file syntax when placeholders and single-quote characters were used in the translations; a common problem in the French translations. However, this has improved massively, and we haven't seen any translation-incurred build errors in a long time. Yet, we stick to the process. |
|
| 72 | + |
|
| 73 | +Roll-out to the landscape can take place after the master job has built a release successfully. |
|
| 74 | + |
|
| 75 | +The Github webhook CGI script is pointed to by the ``/etc/httpd/conf.d/004-git-ssl.conf`` configuration which has to enable symbolic links for its CGI directory (``Options FollowSymLinks``). The ``ScriptAlias`` in the configuration is defined like this: |
|
| 76 | + |
|
| 77 | +``` |
|
| 78 | +ScriptAlias /hooks/ /var/www/cgi-bin/ |
|
| 79 | +<Directory "/var/www/cgi-bin"> |
|
| 80 | + SSLOptions +StdEnvVars |
|
| 81 | + AllowOverride None |
|
| 82 | + Options FollowSymLinks |
|
| 83 | + Order allow,deny |
|
| 84 | + Allow from all |
|
| 85 | +</Directory> |
|
| 86 | +``` |
|
| 87 | + |
|
| 88 | +letting the webhook react to the URL ``https://git.sapsailing.com/hooks/github.cgi``. The Github hook configuration must be set to use ``application/json`` as the content type as the CGI script parses pusher and ref from the request body that is expected to be in JSON format. |
|
| 89 | + |
|
| 90 | +## Adding New Collections of Translatable Strings |
|
| 91 | + |
|
| 92 | +Follow the patterns you find in the ``translation_v2.json`` file at the root of our Git repository and make sure to inform LXLabs (currently vyacheslav.sklyarenko@sap.com, called "Slava"). Make sure you have your default ("en") and German ("de") locale-specific files in place and checked into Git. Once LXLabs has confirmed the creation of the new collection, push your changes to the [https://github.tools.sap/SAP-Sailing-Analytics/sapsailing/tree/translation](https://github.tools.sap/SAP-Sailing-Analytics/sapsailing/tree/translation) branch and wait for the translations to come back. |
|
| 93 | + |
|
| 94 | +## Adding Locales |
|
| 95 | + |
|
| 96 | +This comes with two aspects. |
|
| 97 | + |
|
| 98 | +As the first aspect, GWT requires us to "declare" the locales we support. We currently do this in two files which serve as the common ancestor in the GWT module inheritance hierarchy for the two major areas that contribute end user-facing entry points: |
|
| 99 | + |
|
| 100 | +- ``java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SailingLocalesAllPermutations.gwt.xml`` |
|
| 101 | +- ``java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/SecurityLocalesAllPermutations.gwt.xml`` |
|
| 102 | + |
|
| 103 | +They both have a sibling file ``...SinglePermutation.gwt.xml`` which is used for local / fast builds where only one browser permutation for the default "en" locale is defined. |
|
| 104 | + |
|
| 105 | +The list of locales supported is specified in a line like this: |
|
| 106 | + |
|
| 107 | +``` |
|
| 108 | + <extend-property name="locale" values="en,de,zh,ru,ja,es,fr,pt,it,da,sl,cs" /> |
|
| 109 | +``` |
|
| 110 | + |
|
| 111 | +Add your locale's two-letter key to the end of this list, separated by a comma. |
|
| 112 | + |
|
| 113 | +The second aspect is to request the translation into the new locale from LXLabs (vyacheslav.sklyarenko@sap.com, called "Slava"). This typically comes at a price tag of around 5k Euro for each new language. If the cost center owner approves, LXLabs bills the language addition to that cost center, and a few weeks later the new translations will be delivered to the ``translation`` branch. |
|
| ... | ... | \ No newline at end of file |
wiki/howto/onboarding.md
| ... | ... | @@ -44,11 +44,11 @@ First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements |
| 44 | 44 | ### Installations |
| 45 | 45 | |
| 46 | 46 | 1. JDK >= 11. No longer required by Eclipse because new Eclipse releases bring their own JDK bundled with the installer. Still, if you want to use Java 11 or Java 17, install any such JDK and set the `JAVA_HOME` variable to it. |
| 47 | -2. Eclipse IDE for Eclipse Committers, version 4.26.0 ["2022-12"](https://www.eclipse.org/downloads/packages/release/2022-12/r/eclipse-ide-eclipse-committers) |
|
| 47 | +2. Eclipse IDE for Eclipse Committers, version 4.29.0 ["2023-09"](https://www.eclipse.org/downloads/packages/release/2023-09/r) |
|
| 48 | 48 | 3. JDK 1.8 (Java SE 8), ideal is the SAPJVM 1.8: Go to [https://tools.eu1.hana.ondemand.com/#cloud](https://tools.eu1.hana.ondemand.com/#cloud), scroll down to `SAP JVM` select your operating System, extract the downloaded .zip into desired location (e.g. Windows `C:\Program Files\Java`. If you want to make this your default JDK, set the `JAVA_HOME` variable to it. In any case, set the `JAVA8_HOME` variable to it which is required by a few build scripts where certain steps currently are not yet compatible with newer JDK releases, such as our Android build process, keeping us on Gradle 6.0.1 for the time being which isn't Java 17-compatible. |
| 49 | 49 | 4. Git (e.g. Git for Windows v2.18), [http://git-scm.com](http://git-scm.com) / [https://git-for-windows.github.io](https://git-for-windows.github.io) |
| 50 | 50 | 5. Configure git (see [Git repository configuration essentials](#onboarding-information_sap-sailing-analytics-development-setup_git-repository-configuration-essentials)) |
| 51 | -6. MongoDB (at least Release 4.4), download: [https://www.mongodb.com/](https://www.mongodb.com/) |
|
| 51 | +6. MongoDB (at least Release 4.4), download: [https://www.mongodb.com/](https://www.mongodb.com/). You may need to choose the community edition. In addition, install `mongosh`. |
|
| 52 | 52 | 7. RabbitMQ, download from [http://www.rabbitmq.com](http://www.rabbitmq.com). Requires Erlang to be installed. RabbitMQ installer will assist in installing Erlang. Some sources report that there may be trouble with the latest versions of RabbitMQ. In some cases, McAffee seems to block the installation of the latest version on SAP hardware; in other cases connection problems to the newest versions have been reported. We know that version 3.6.8 works well. [https://github.com/rabbitmq/rabbitmq-server/releases/tag/rabbitmq_v3_6_8](https://github.com/rabbitmq/rabbitmq-server/releases/tag/rabbitmq_v3_6_8) |
| 53 | 53 | 8. Maven 3.1.1 (or higher), [http://maven.apache.org](http://maven.apache.org) |
| 54 | 54 | A setup guide for windows can be found on this webpage: [https://maven.apache.org/guides/getting-started/windows-prerequisites.html](https://maven.apache.org/guides/getting-started/windows-prerequisites.html) |
| ... | ... | @@ -113,6 +113,7 @@ Out of the box, multiple settings in Eclipse need to be changed. Go to Window |
| 113 | 113 | - In "General ⇒ Content Types" select on CSS (Text ⇒ CSS) and add \*.gss in the lower file association list to get limited syntax highlighting and content assist in GSS files |
| 114 | 114 | - In "General ⇒ Editors ⇒ Text Editors" check Insert Spaces for Tabs |
| 115 | 115 | - In "General ⇒ Editors ⇒ Text Editors ⇒ Quick Diff" change the reference source from 'Version on Disk' to 'A Git Revision'. If you like other colours for marking diffs change them here. (Example: Changes = Yellow, Additions = Green, Deletions = Red) |
| 116 | +- If you'd like to be able to import official results from the Manage2Sail regatta management system: In Run/Debug ⇒ String Substitution add a variable ``MANAGE2SAIL_ACCESS_TOKEN``. Ask your team lead for the value of such an access token you can uses for testing. The variable is used by the "Sailing Server (No Proxy)" launch configuration. and maybe others. |
|
| 116 | 117 | - In "GWT ⇒ Errors/Warnings" set "Missing SDK" to "Ignore" |
| 117 | 118 | - In "GWT ⇒ GWT Settings ⇒ Add..." add the GWT SDK you downloaded and unpacked earlier |
| 118 | 119 | - In "Java ⇒ Build Path ⇒ Classpath Variables" create a new classpath variable called `ANDROID_HOME`. Set its value to the installation location of your Android SDK, e.g., `C:\Users\'user'\AppData\Local\Android\Sdk` or `/usr/local/android-sdk-linux`. |
| ... | ... | @@ -245,13 +246,14 @@ See [RaceCommittee App](/wiki/info/mobile/racecommittee-app) for more informatio |
| 245 | 246 | |
| 246 | 247 | Irritating behavior can occur on ARM-based processors, such as on a MacBook. There are some problems, especially with graphical functions. There are cases where javax.imageio.ImageIO, javax.imageio.ImageReader or java.awt.graphics2D stops responding without error message. |
| 247 | 248 | In such cases it might help to set AWT to headless mode (`-Djava.awt.headless=true`, see [stackoverflow](https://stackoverflow.com/questions/13796611/imageio-read-with-mac for more information)). |
| 249 | +Another struggle can be to install the JVM Profiler Plug-in on ARM based Eclipse. It seems to block the necessary Software while the Automatic Eclipse plugin installation. One way is to use the Eclipse x64 Installer. It will take more time for transformation, but you will be able to use the Profiler. |
|
| 248 | 250 | |
| 249 | 251 | ### GWT Browser Plugin |
| 250 | 252 | Install the GWT Browser Plugin for the GWT Development mode. As of 2016-08-31 Firefox is the only browser supporting the GWT plugin, you have to download Firefox version 24 for it to work. The Plugin can be found on this page: [https://code.google.com/archive/p/google-web-toolkit/downloads](https://code.google.com/archive/p/google-web-toolkit/downloads) |
| 251 | 253 | |
| 252 | 254 | ### Create Hudson Job |
| 253 | 255 | If you want a hudson job to run when you push your branch then you can run a script in `configuration` called . Run options for a branch titled `createHudsonJobForBug.sh`. For you bug branch titled `bug<bug number>`, create a build job, which will create a release, by running the script like so: `./createHudsonJobForBug.sh <bug number>`. |
| 254 | -If on Windows, you may need to disable any web shields in antivirus software, to allow `curl` to function. |
|
| 256 | +If on Windows, you may need to disable any web shields in antivirus software, to allow `curl` to function. If on Mac, you may need to install gnu-sed. |
|
| 255 | 257 | |
| 256 | 258 | ###Issues when playing around with AWS |
| 257 | 259 | - The problem: **aws cli (used for aws ec2 describe-tags) hangs in eu-west-2** in all AZs on new instances I created, using a target group which permitted all outbound connections and inbound https, http and ssh connections. I tried permitting everything but that didn’t work. When I attached (at Axel’s suggestion) the Java Application with Reverse Proxy security group, it worked but — even if I duplicated this security group, and applied that copy instead — it still didn’t work. |
wiki/info/landscape/archive-server-upgrade.md
| ... | ... | @@ -2,15 +2,22 @@ |
| 2 | 2 | |
| 3 | 3 | ## TL;DR |
| 4 | 4 | |
| 5 | -- Launch MongoDB replica for ``archive`` replica set (see [here](https://security-service.sapsailing.com/gwt/AdminConsole.html#LandscapeManagementPlace:)) |
|
| 6 | -- Wait until MongoDB replica is in ``SECONDARY`` state |
|
| 5 | +- Optionally, to accelerate DB reads, launch MongoDB replica for ``archive`` replica set (see [here](https://security-service.sapsailing.com/gwt/AdminConsole.html#LandscapeManagementPlace:)); wait until MongoDB replica is in ``SECONDARY`` state |
|
| 7 | 6 | - Launch "more like this" based on existing primary archive, adjusting the ``INSTALL_FROM_RELEASE`` user data entry to the release of choice and the ``Name`` tag to "SL Archive (New Candidate)" |
| 8 | -- Wait until the new instance is done with its background tasks and CPU utilization goes to 0% (approximately 24h) |
|
| 7 | +- Wait until the new instance is done with its background tasks and CPU utilization goes to 0% (approximately 36h) |
|
| 8 | +- Create an entry in the reverse proxy's ``/etc/httpd/conf.d/001-events.conf`` file like this: |
|
| 9 | +``` |
|
| 10 | + Use Plain archive-candidate.sapsailing.com 172.31.46.203 8888 |
|
| 11 | +``` |
|
| 12 | +with ``172.31.46.203`` being an example of the internal IP address your new archive candidate instance got assigned. |
|
| 9 | 13 | - Compare server contents, either with ``compareServers`` script or through REST API, and fix any differences |
| 14 | +``` |
|
| 15 | + java/target/compareServers -ael https://www.sapsailing.com https://archive-candidate.sapsailing.com |
|
| 16 | +``` |
|
| 10 | 17 | - Do some spot checks on the new instance |
| 11 | 18 | - Switch reverse proxy by adjusting ``ArchiveRewrite`` macro under ``root@sapsailing.com:/etc/httpd/conf.d/000-macros.conf`` followed by ``service httpd reload`` |
| 12 | -- Terminate old fail-over EC2 instance |
|
| 13 | -- adjust Name tags for what is now the fail-over and what is now the primary archive server in EC2 console |
|
| 19 | +- Terminate old fail-over EC2 instance; you will have to disabel its termination protection first. |
|
| 20 | +- Adjust Name tags for what is now the fail-over and what is now the primary archive server in EC2 console |
|
| 14 | 21 | |
| 15 | 22 | [[_TOC_]] |
| 16 | 23 | |
| ... | ... | @@ -61,6 +68,16 @@ INFO: Thread[MarkPassingCalculator for race R14 initialization,4,main]: Timeout |
| 61 | 68 | ``` |
| 62 | 69 | that will keep repeating. Watch out for the ``queued tasks`` count. It should be decreasing, and when done it should go down to 0 eventually. |
| 63 | 70 | |
| 71 | +### Create a Temporary Mapping in ``/etc/httpd/conf.d/001-events.conf`` to Make New Server Accessible Before Switching |
|
| 72 | + |
|
| 73 | +Grab the internal IP address of your freshly launched archive server candidate (something like 172.31.x.x) and ensure you have a line of the form |
|
| 74 | + |
|
| 75 | +``` |
|
| 76 | + Use Plain archive-candidate.sapsailing.com 172.31.35.213 8888 |
|
| 77 | +``` |
|
| 78 | + |
|
| 79 | +in the file ``root@sapsailing.com:/etc/httpd/conf.d/001-events.conf``, preferably towards the top of the file where it can be quickly found. Save the changes and check the configuration using the ``apachectl configtest`` command. It should give an output saying ``Syntax OK``. Only in this case reload the configuration by issuing the ``service httpd reload`` command as user ``root``. After this command has completed, you can watch your archive server candidate start up at [https://archive-candidate.sapsailing.com/gwt/status](https://archive-candidate.sapsailing.com/gwt/status) and make any changes necessary when the ``compareServers`` script (see below) notifies you of any differences that need handling. |
|
| 80 | + |
|
| 64 | 81 | ### Comparing Contents with Primary ARCHIVE Server |
| 65 | 82 | |
| 66 | 83 | This is when you can move on to comparing the new candidate's contents with the primary archive server. Two ways are currently possible. |
| ... | ... | @@ -94,6 +111,8 @@ Differences are reported in a JSON response document. If no differences are foun |
| 94 | 111 | ``` |
| 95 | 112 | In this example, ``34.242.227.113`` would be the public IP address of the server that responded to the ``www.sapsailing.com`` request (the current primary archive server), and ``1.2.3.4`` would be the public IP address of your new candidate archive server. The response status will be ``200`` if the comparison was ok, and ``409`` otherwise. Handle differences are described for the ``compareServers`` script above. |
| 96 | 113 | |
| 114 | +You can also trigger the REST API-based comparison by using the ``-a`` option of the ``compareServers`` script. |
|
| 115 | + |
|
| 97 | 116 | ### Manual Spot Checks |
| 98 | 117 | |
| 99 | 118 | Following the mandatory automated content comparison you should do a few spot checks on the new archive server candidate. Go to ``http://1.2.3.4:8888/gwt/Home.html`` if ``1.2.3.4`` is the public IP address of your new archive server candidate and browse through a few events. Note that clicking on a link to show all events will get you back to ``www.sapsailing.com``. In this case, replace ``www.sapsailing.com`` by your candidate server's public IP address again and continue browsing. |
wiki/info/landscape/creating-ec2-image-for-webserver-from-scratch.md
| ... | ... | @@ -172,28 +172,24 @@ maxretry = 5 |
| 172 | 172 | |
| 173 | 173 | ## Basic setup for reverse proxy instance |
| 174 | 174 | |
| 175 | -perl |
|
| 176 | -httpd |
|
| 177 | -apachetop |
|
| 178 | -per-CGI |
|
| 179 | -postfix |
|
| 180 | -sendmail-cf |
|
| 181 | - mod_proxy_html |
|
| 182 | -tmux |
|
| 183 | -nfs-utils |
|
| 184 | -amazon linux extras install php7.2 && yum update && yum install php |
|
| 175 | +From a fresh amazon linux 2023 instance (HVM) install perl, httpd, mod_proxy_html, tmux, nfs-utils, git, whois and jq. Then type `amazon-linux-extras install epel`, which adds the epel repo so you can then run install apachetop. |
|
| 176 | +Then you need to remove the automatic ec2 code which disabled root access; reconfigure the sshd_config; setup the keys update script; and initialise the crontab. Store a bearer token in the home dir. |
|
| 185 | 177 | |
| 178 | +Rename the welcome.conf. Add a basic web page, as the Apache default page can sometimes return no 2xx codes, which can lead to failing health checks. |
|
| 186 | 179 | |
| 187 | -root access |
|
| 188 | -num of connections trackable |
|
| 189 | -keyreader token |
|
| 180 | +Setup fail2ban like above. |
|
| 190 | 181 | |
| 191 | -amazon-efs-utils |
|
| 192 | -mounts |
|
| 182 | +Ensure httpd is enabled, so that the server auto starts upon a restart. |
|
| 183 | +Other modules may need to be installed, depending on the httpd config. |
|
| 193 | 184 | |
| 194 | -mail -> I don't think anything is needed but I added the stuf to sendmail-cf /// |
|
| 185 | +Configure a startup service (either in /etc/systemd/system or etc/rcX directories) to try to mount an attached nvme as swap space (this step needs to be checked after setup). |
|
| 186 | +Swap space still needs to be fully automated. |
|
| 187 | + |
|
| 188 | +Postmail is useful. The script for this procedure is in configuration and is titled setupDisposableProxy.sh |
|
| 189 | + |
|
| 190 | +Setup the logrotate target. |
|
| 191 | + |
|
| 192 | +Setup the fstab (not automated). |
|
| 193 | +Update amazon cli (because pricing list requires it) |
|
| 195 | 194 | |
| 196 | -logrotate target |
|
| 197 | 195 | |
| 198 | -chkconfig nfs on (half complete) |
|
| 199 | -fail2ban setup |
|
| ... | ... | \ No newline at end of file |
wiki/info/landscape/upgrading-archive-server.md
| ... | ... | @@ -1,113 +0,0 @@ |
| 1 | -# Upgrading the Archive Server |
|
| 2 | - |
|
| 3 | -[[_TOC_]] |
|
| 4 | - |
|
| 5 | -For the archive server we currently use two EC2 instances: one as the production server, and a second one as a failover instance. The failover server is usually one version "behind" the production server which is meant to allow for recovering fast from regressions introduced accidentally by an upgrade. With startup cycles that take approximately 24h as of this writing it is no option to hope to recover by starting another instance "fast." |
|
| 6 | - |
|
| 7 | -The mapping for which archive server to use is encoded in the central web server's Apache reverse proxy configuration under ``/etc/httpd/conf.d/000-macros.conf``. At its head there is a configuration like this: |
|
| 8 | - |
|
| 9 | -``` |
|
| 10 | -<Macro ArchiveRewrite> |
|
| 11 | -# ARCHIVE, based on i3.2xlarge, 64GB RAM and 1.9TB swap |
|
| 12 | - Use Rewrite 172.31.26.254 8888 |
|
| 13 | -</Macro> |
|
| 14 | -``` |
|
| 15 | - |
|
| 16 | -It defines the ``ArchiveRewrite`` macro which is then used by other macros such as ``Series-ARCHIVE`` and ``Event-ARCHIVE``, used then in ``/etc/httpd/conf.d/001-events.conf``. |
|
| 17 | - |
|
| 18 | -The process of upgrading the archive server works in the following steps: |
|
| 19 | -* Scale out the underlying ``archive`` MongoDB replica set |
|
| 20 | -* Launch a new EC2 instance with the necessary archive server configuration for the new release you'd like to upgrade to |
|
| 21 | -* Create a temporary mapping in ``/etc/httpd/conf.d/001-events.conf`` to make new server accessible before switching |
|
| 22 | -* Wait for the new candidate server to have finished loading |
|
| 23 | -* Compare contents to running archive |
|
| 24 | -* Switch ``ArchiveRewrite`` macro to new instance |
|
| 25 | -* Reload Apache httpd service |
|
| 26 | -* Re-label EC2 instances and shut down old failover server |
|
| 27 | - |
|
| 28 | -The following sections explain the steps in a bit more detail. |
|
| 29 | - |
|
| 30 | -## Scale out the underlying ``archive`` MongoDB replica set |
|
| 31 | - |
|
| 32 | -When not launching an archive server, the ``archive`` replica set runs on a small, inexpensive but slow instance ``dbserver.internal.sapsailing.com:10201`` with which launching a new archive server takes days and weeks. In order to accelerate loading all content, first two or three replicas need to be added. Either use the ``configuration/aws-automation/launch-mongodb-replica.sh`` script from your git workspace as many times as you want to have replicas: |
|
| 33 | - |
|
| 34 | -``` |
|
| 35 | - ./configuration/aws-automation/launch-mongodb-replica.sh -r archive -p dbserver.internal.sapsailing.com:10201 -P 0 -t i3.2xlarge -k {your-key-name} |
|
| 36 | -``` |
|
| 37 | - |
|
| 38 | -Or use [https://security-service.sapsailing.com/gwt/AdminConsole.html?locale=en#LandscapeManagementPlace:](https://security-service.sapsailing.com/gwt/AdminConsole.html?locale=en#LandscapeManagementPlace:) and log on with your account, make sure you have an EC2 key registered, then select the ``archive`` replica set under "MongoDB Endpoints" and click on the "Scale in/out" action button, then choose the number of replicas you'd like to launch and confirm. |
|
| 39 | - |
|
| 40 | -Once triggered, you can watch the instances start up in the AWS console under EC2 instances. The filling of those replicas may take a few hours; you may check progress by connecting to a host in the landscape with the ``mongo`` client installed and try something like this: |
|
| 41 | - |
|
| 42 | -``` |
|
| 43 | - $ mongo "mongodb://dbserver.internal.sapsailing.com:10201/?replicaSet=archive&retryWrites=true&readPreference=nearest" |
|
| 44 | - ... |
|
| 45 | - archive:PRIMARY> rs.status() |
|
| 46 | - archive:PRIMARY> quit() |
|
| 47 | -``` |
|
| 48 | - |
|
| 49 | -This will output a JSON document listing the members of the ``archive`` replica set. You should see a ``stateStr`` attribute which usually is one of ``PRIMARY``, ``SECONDARY``, or ``STARTUP2``. Those ``STARTUP2`` instances are in the process of receiving an initial load from the replica set and are not yet ready to receive requests. If you'd like to minimize the actual start-up time of your archive server process later, wait until all replicas have reached the ``SECONDARY`` state. But it's no problem, either, to launch the new archive server before that, only it will take longer to launch, and probably the additional load on the replica set will make the replicas reach the ``SECONDARY`` state even later. |
|
| 50 | - |
|
| 51 | -## Launch a new EC2 instance |
|
| 52 | - |
|
| 53 | -Use an instance of type ``i3.2xlarge`` or larger. The most important thing is to pick an "i" instance type that has fast NVMe disks attached. Those will be configured automatically as swap space. You may select "Launch more like this" based on the current production archive server to start with. Use the following as the user data: |
|
| 54 | - |
|
| 55 | -``` |
|
| 56 | -INSTALL_FROM_RELEASE={build-...} |
|
| 57 | -USE_ENVIRONMENT=archive-server |
|
| 58 | -REPLICATE_MASTER_BEARER_TOKEN="***" |
|
| 59 | -``` |
|
| 60 | - |
|
| 61 | -Obtain the actual build version, e.g., from looking at [https://releases.sapsailing.com](https://releases.sapsailing.com), picking the newest ``build-...`` release. The Java heap size is currently set at 200GB in [https://releases.sapsailing.com/environments/archive-server](https://releases.sapsailing.com/environments/archive-server). You can adjust this by either adding a ``MEMORY=...`` assignment to your user data, overriding the value coming from the ``archive-server`` environment, or you may consider adjusting the ``archive-server`` environment for good by editing ``trac@sapsailing.com:releases/environments/archive-server``. |
|
| 62 | - |
|
| 63 | -Launching the instance in our default availability zone (AZ) ``eu-west-1c`` may be preferable as it minimizes cross-AZ traffic since the central web server / reverse proxy is also located in that AZ. However, this is not absolutely necessary, and you may also consider it a good choice to launch the new candidate in an AZ explicitly different from the current production archive server's AZ in order to increase chances for high availability should an AZ die. |
|
| 64 | - |
|
| 65 | -Tag the instance with ``Name=SL Archive (New Candidate)`` and ``sailing-analytics-server=ARCHIVE``. You may have inherited these tags (except with the wrong name) in case you used "Launch more like this" before. |
|
| 66 | - |
|
| 67 | -## Create a temporary mapping in ``/etc/httpd/conf.d/001-events.conf`` to make new server accessible before switching |
|
| 68 | - |
|
| 69 | -Grab the internal IP address of your freshly launched archive server candidate (something like 172.31.x.x) and ensure you have a line of the form |
|
| 70 | - |
|
| 71 | -``` |
|
| 72 | - Use Plain archive-candidate.sapsailing.com 172.31.35.213 8888 |
|
| 73 | -``` |
|
| 74 | - |
|
| 75 | -in the file ``root@sapsailing.com:/etc/httpd/conf.d/001-events.conf``, preferably towards the top of the file where it can be quickly found. Save the changes and check the configuration using the ``apachectl configtest`` command. It should give an output saying ``Syntax OK``. Only in this case reload the configuration by issuing the ``service httpd reload`` command as user ``root`. After this command has completed, you can watch your archive server candidate start up at [https://archive-candidate.sapsailing.com/gwt/status](https://archive-candidate.sapsailing.com/gwt/status). |
|
| 76 | - |
|
| 77 | -## Wait for the new candidate server to have finished loading |
|
| 78 | - |
|
| 79 | -At the top of the ``/gwt/status`` output you'll see the fields ``numberofracestorestore`` and the ``numberofracesrestored``. The ``/gwt/status`` health check will respond with a status 200 only after the ``numberofracesrestored`` has reached the ``numberofracestorestore``. But even after that the new archive server candidate will be busy for a few more hours. The ``numberofracesrestored`` count only tells you the loading of how many races has been *triggered*, not necessarily how many of those have already *completed* loading. Furthermore, even when all content has been loaded, calculations will keep going on for a few more hours, re-establishing information about wind estimation, mark passings, and maneuvers. You can follow the CPU load of the candidate server in the AWS EC2 console. Select your new instance and switch to the Monitoring tab, there watch for the "CPU Utilization" and wait for it to consistently drop from approximately 100% down to 0%. This indicates the end of the loading process, and you can now proceed to the next step. |
|
| 80 | - |
|
| 81 | -## Compare contents to running archive |
|
| 82 | - |
|
| 83 | -It is a good idea to ensure all races really have loaded successfully, and all wind data and GPS data is available again, just like in the current production archive server. For this purpose, the ``java/target/compareServers`` script exists in your git workspace. Call like this: |
|
| 84 | - |
|
| 85 | -``` |
|
| 86 | - java/target/compareServers -el https://www.sapsailing.com https://archive-candidate.sapsailing.com |
|
| 87 | -``` |
|
| 88 | - |
|
| 89 | -This assumes, of course, that you have completed the step explained above for establishing HTTPS access to ``archive-candidate.sapsailing.com``. |
|
| 90 | - |
|
| 91 | -The script will fetch leaderboard group by leaderboard group and compare their contents, race loading state, and presence or absence of tracking and wind data for all races reachable through those leaderboard groups. The names of the leaderboard groups will be printed to the standard output. If a difference is found, the script will exit, showing the difference on the console. You then have a chance to fix the problem by logging on to [https://archive-candidate.sapsailing.com/gwt/AdminConsole.html](https://archive-candidate.sapsailing.com/gwt/AdminConsole.html). Assuming you were able to fix the problem, continue the comparison by calling the ``compareServers`` script again, adding the ``-c`` option ("continue"), like this: |
|
| 92 | - |
|
| 93 | - ``` |
|
| 94 | - java/target/compareServers -cel https://www.sapsailing.com https://archive-candidate.sapsailing.com |
|
| 95 | -``` |
|
| 96 | - |
|
| 97 | -This will continue with the comparison starting with the leaderboard group where a difference was found in the previous run. |
|
| 98 | - |
|
| 99 | -Should you encounter a situation that you think you cannot or don't want to fix, you have to edit the ``leaderboardgroups.old.sed`` and ``leaderboardgroups.new.sed`` files and remove the leaderboard group(s) you want to exclude from continued comparisons. You find the leaderboard group that produced the last difference usually at the top of the two files. Once saved, continue again with the ``-c`` option as shown above. |
|
| 100 | - |
|
| 101 | -Once the script has completed successfully you may want to do a few spot checks on [https://archive-candidate.sapsailing.com](https://archive-candidate.sapsailing.com). Note, that when you navigate around, some links may throw you back to ``www.sapsailing.com``. There, you manually have to adjust your browser address bar to ``archive-candidate.sapsailing.com`` to continue looking at your new candidate archive server contents. |
|
| 102 | - |
|
| 103 | -## Switch ``ArchiveRewrite`` macro to new instance and reload httpd |
|
| 104 | - |
|
| 105 | -Once you're satisfied with the contents of your new candidate it's time to switch. Log on as ``root@sapsailing.com`` and adjust the ``/etc/httpd/conf.d/000-macros.conf`` file, providing the new internal server IP address in the ``ArchiveRewrite`` macro at the top of the file. Save and check the configuration again using ``apachectl configtest``. If the syntax is OK, reload the configuration with ``service httpd reload`` and inform the community by a corresponding JAM post, ideally providing the release to which you upgraded. |
|
| 106 | - |
|
| 107 | -## Re-label EC2 instances and shut down old failover server |
|
| 108 | - |
|
| 109 | -Finally, it's time to shut down the old failover instance which is now no longer needed. Go to the AWS EC2 console, look at the running instances and identify the one labeled "SL Archive (Failover)". In the "Instance Settings" disable the termination protection for this instance, then terminate. |
|
| 110 | - |
|
| 111 | -Rename the "SL Archive" instance to "SL Archive (Failover)", then rename "SL Archive (New Candidate)" to "SL Archive". |
|
| 112 | - |
|
| 113 | -Then, enable termination protection for your new "SL Archive" server if it isn't already enabled because you used "Launch more like this" to create the instance using the previous production archive server as a template. |
|
| ... | ... | \ No newline at end of file |