.github/workflows/create-docker-image.yml
... ...
@@ -6,7 +6,7 @@ on:
6 6
workflow_run:
7 7
workflows: [release]
8 8
types: [completed]
9
- branches: [main, docker-24]
9
+ branches: [main, docker-24, docker-25]
10 10
workflow_dispatch:
11 11
inputs:
12 12
release:
... ...
@@ -72,12 +72,12 @@ jobs:
72 72
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
73 73
with:
74 74
build-args: RELEASE=${{ env.RELEASE }}
75
- tags: ghcr.io/${{steps.ghcr.outputs.PACKAGE}}:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-17', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-21', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-24', steps.ghcr.outputs.PACKAGE) || '' }}
75
+ tags: ghcr.io/${{steps.ghcr.outputs.PACKAGE}}:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-17', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-21', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-24', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-25' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-25', steps.ghcr.outputs.PACKAGE) || '' }}
76 76
annotations: |
77 77
maintainer=axel.uhl@sap.com
78 78
index:org.opencontainers.image.title=Sailing Analytics
79 79
index:org.opencontainers.image.description=The Sailing Analytics Web Application
80
- file: ${{ env.BRANCH == 'docker-17' && 'docker/Dockerfile_sapsailing_on_sapmachine17' || env.BRANCH == 'docker-21' && 'docker/Dockerfile_sapsailing_on_sapmachine21' || env.BRANCH == 'docker-24' && 'docker/Dockerfile_sapsailing_on_sapmachine24' || 'docker/Dockerfile' }}
80
+ file: ${{ env.BRANCH == 'docker-17' && 'docker/Dockerfile_sapsailing_on_sapmachine17' || env.BRANCH == 'docker-21' && 'docker/Dockerfile_sapsailing_on_sapmachine21' || env.BRANCH == 'docker-24' && 'docker/Dockerfile_sapsailing_on_sapmachine24' || env.BRANCH == 'docker-25' && 'docker/Dockerfile_sapsailing_on_sapmachine25' || 'docker/Dockerfile' }}
81 81
context: docker/
82 82
platforms: linux/amd64,linux/arm64
83 83
push: true
.github/workflows/merge-main-into-docker-25.yml
... ...
@@ -0,0 +1,29 @@
1
+name: Merge main branch into docker-25 after successful build
2
+on:
3
+ workflow_run:
4
+ workflows: [release]
5
+ types: [completed]
6
+ branches: [main]
7
+ workflow_dispatch: {}
8
+jobs:
9
+ merge-main-into-docker-25:
10
+ permissions:
11
+ contents: write
12
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+ with:
18
+ ref: docker-25
19
+ fetch-depth: 0 # fetch the whole thing to make sure the histories merge
20
+ - name: Merge main into docker-25
21
+ uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f # v1.4.0
22
+ env:
23
+ GH_TOKEN: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
24
+ with:
25
+ type: now
26
+ from_branch: main
27
+ target_branch: docker-25
28
+ message: Auto-merging main into docker-25 after successful release build
29
+ github_token: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
.github/workflows/release.yml
... ...
@@ -78,12 +78,12 @@ jobs:
78 78
distribution: 'temurin' # See 'Supported distributions' for available options
79 79
java-version: '8'
80 80
mvn-toolchain-id: 'JavaSE-1.8'
81
- - name: Install JDK 17
81
+ - name: Install JDK 25
82 82
uses: actions/setup-java@v4
83 83
with:
84 84
distribution: 'temurin' # See 'Supported distributions' for available options
85
- java-version: '17'
86
- mvn-toolchain-id: 'JavaSE-17'
85
+ java-version: '25'
86
+ mvn-toolchain-id: 'JavaSE-25'
87 87
- name: Setup Android SDK
88 88
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2
89 89
with:
... ...
@@ -131,7 +131,7 @@ jobs:
131 131
reporter: java-junit
132 132
fail-on-error: true
133 133
- name: Build Release
134
- if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || startsWith(github.ref, 'refs/heads/releases/') }}
134
+ if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }}
135 135
shell: bash
136 136
run: |
137 137
./configuration/buildAndUpdateProduct.sh -u -L ${{ github.event.inputs.skip_tests == 'true' && '-n untested' || '' }} release
... ...
@@ -148,14 +148,14 @@ jobs:
148 148
retention-days: 90
149 149
- name: Upload distribution artifact
150 150
uses: actions/upload-artifact@v4
151
- if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || startsWith(github.ref, 'refs/heads/releases/') }}
151
+ if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }}
152 152
with:
153 153
name: ${{ env.SIMPLE_VERSION_INFO }}
154 154
path: dist/**/*
155 155
retention-days: 90
156 156
- name: Create Release
157 157
id: create_release
158
- if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || startsWith(github.ref, 'refs/heads/releases/') }}
158
+ if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }}
159 159
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
160 160
with:
161 161
tag_name: ${{ env.SIMPLE_VERSION_INFO }}
... ...
@@ -164,7 +164,7 @@ jobs:
164 164
prerelease: false
165 165
- name: Upload Release Asset tar.gz
166 166
id: upload-release-asset-tar-gz
167
- if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || startsWith(github.ref, 'refs/heads/releases/') }}
167
+ if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }}
168 168
uses: actions/upload-release-asset@v1
169 169
env:
170 170
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
... ...
@@ -175,7 +175,7 @@ jobs:
175 175
asset_content_type: application/x-tar
176 176
- name: Upload Release Asset release_notes.txt
177 177
id: upload-release-asset-release-notes-txt
178
- if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || startsWith(github.ref, 'refs/heads/releases/') }}
178
+ if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }}
179 179
uses: actions/upload-release-asset@v1
180 180
env:
181 181
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
configuration/createHudsonJobForBug.sh
... ...
@@ -4,9 +4,12 @@ if [ $# -eq 0 ]; then
4 4
echo ""
5 5
echo
6 6
echo "Constructs a Hudson job for the given bugid"
7
- echo "Example: $0 4221"
7
+ echo "Example: $0 4221 [ {Bugzilla-API-Key} ]"
8 8
echo "Builds a Hudson job for bug branch bug4221, linking to the Bugzilla bug and copying a release"
9
- echo "from Github to https://releases.sapsailing.com if the Github Actions Workflow built one"
9
+ echo "from Github to https://releases.sapsailing.com if the Github Actions Workflow built one."
10
+ echo "If a Bugzilla API Key is provided (may also be specified in the BUGZILLA_API_KEY environment"
11
+ echo "variable), it is used to add the bug summary to the build job's description."
12
+ echo "Get a Bugzilla API Key for your user account at https://bugzilla.sapsailing.com/bugzilla/userprefs.cgi?tab=apikey"
10 13
exit 2
11 14
fi
12 15
... ...
@@ -18,6 +21,16 @@ HUDSON_BASE_URL=https://hudson.sapsailing.com
18 21
BUGZILLA_BASE=https://bugzilla.sapsailing.com/bugzilla
19 22
COPY_TEMPLATE_JOB=CopyTemplate
20 23
OS_FOR_GSED="darwin"
24
+if [ -n "$2" ]; then
25
+ BUGZILLA_API_KEY="$2"
26
+fi
27
+if [ -n "${BUGZILLA_API_KEY}" ]; then
28
+ echo "Trying to obtain bug summary/title from Bugzilla..."
29
+ BUG_SUMMARY="$( curl -s -H 'Content-Type: application/json' -H 'Accept: application/json' ${BUGZILLA_BASE}'/rest/bug/'${BUG_ID}'?Bugzilla_api_key='${BUGZILLA_API_KEY}'&include_fields=summary' | jq -r '.bugs[0].summary' )"
30
+ echo "Found: ${BUG_SUMMARY}"
31
+else
32
+ BUG_SUMMARY=""
33
+fi
21 34
read -p "Username: " USERNAME
22 35
read -s -p "Password: " PASSWORD
23 36
echo
... ...
@@ -27,9 +40,9 @@ curl -s -X GET $COPY_TEMPLATE_CONFIG_URL -u "$USERNAME:$PASSWORD" -o "$CONFIGFIL
27 40
# On macosx is gnu-sed needed
28 41
if [[ "$OSTYPE" == *"$OS_FOR_GSED"* ]]; then
29 42
echo "Using gsed"
30
- gsed -i'' -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt;. See its latest \&lt;a href=\&quot;/userContent/measurements.html?job=bug'$BUG_ID'\&quot;\&gt;quality and performance measurements here.\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
43
+ gsed -i'' -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt; ('"${BUG_SUMMARY}"'). See its latest \&lt;a href=\&quot;/userContent/measurements.html?job=bug'$BUG_ID'\&quot;\&gt;quality and performance measurements here.\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
31 44
else
32
- sed -i -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt;. See its latest \&lt;a href=\&quot;/userContent/measurements.html?job=bug'$BUG_ID'\&quot;\&gt;quality and performance measurements here.\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
45
+ sed -i -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt; ('"${BUG_SUMMARY}"'). See its latest \&lt;a href=\&quot;/userContent/measurements.html?job=bug'$BUG_ID'\&quot;\&gt;quality and performance measurements here.\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
33 46
fi
34 47
35 48
# On macosx is gnu-sed needed
configuration/environments_scripts/central_reverse_proxy/setup-central-reverse-proxy.sh
... ...
@@ -66,12 +66,14 @@ gem install gollum -v 5.3.2
66 66
gem update --system 3.5.7
67 67
cd /home
68 68
# copy bugzilla
69
-scp -o StrictHostKeyChecking=no root@sapsailing.com:/var/www/static/bugzilla-5.0.4.tar.gz /usr/local/src
69
+scp -o StrictHostKeyChecking=no root@sapsailing.com:/var/www/static/bugzilla-5.2.tar.gz /usr/local/src
70 70
cd /usr/local/src
71
-tar -xzvf bugzilla-5.0.4.tar.gz
72
-mv bugzilla-5.0.4 /usr/share/bugzilla
71
+tar -xzvf bugzilla-5.2.tar.gz
72
+mv bugzilla-5.2 /usr/share/bugzilla
73 73
cd /usr/share/bugzilla/
74
+mkdir data
74 75
scp -o StrictHostKeyChecking=no root@sapsailing.com:/usr/share/bugzilla/localconfig .
76
+scp -o StrictHostKeyChecking=no root@sapsailing.com:/usr/share/bugzilla/data/params.json ./data/
75 77
echo "Bugzilla has been copied. Now setting up bugzilla modules."
76 78
echo "This can take 5 minutes or so. The output is muted (but sent to log.txt) to prevent excessive warnings and clutter in the terminal."
77 79
SECONDEOF
... ...
@@ -79,6 +81,8 @@ terminationCheck "$?"
79 81
ssh -A "root@${IP}" "bash -s" << BUGZILLAEOF &>log.txt
80 82
cd /usr/share/bugzilla/
81 83
# essentials bugzilla
84
+/usr/bin/perl -MCPAN -e 'install App::cpanminus'
85
+cpanm --notest SOAP::Lite
82 86
/usr/bin/perl install-module.pl DateTime
83 87
/usr/bin/perl install-module.pl DateTime::TimeZone
84 88
/usr/bin/perl install-module.pl Email::Sender
... ...
@@ -91,7 +95,9 @@ cd /usr/share/bugzilla/
91 95
/usr/bin/perl install-module.pl Email::Address
92 96
/usr/bin/perl install-module.pl autodie
93 97
/usr/bin/perl install-module.pl Class::XSAccessor
98
+/usr/bin/perl install-module.pl DBIx::Connector
94 99
# nice to have for buzilla
100
+/usr/bin/perl install-module.pl Encode::Detect
95 101
/usr/bin/perl install-module.pl Date::Parse
96 102
/usr/bin/perl install-module.pl Email::Send
97 103
/usr/bin/perl install-module.pl DBI
... ...
@@ -109,6 +115,15 @@ cd /usr/share/bugzilla/
109 115
/usr/bin/perl install-module.pl File::Copy::Recursive
110 116
/usr/bin/perl install-module.pl MIME::Base64
111 117
/usr/bin/perl install-module.pl Authen::SASL
118
+/usr/bin/perl install-module.pl XML::Twig
119
+/usr/bin/perl install-module.pl Net::LDAP
120
+/usr/bin/perl install-module.pl Net::SMTP::SSL
121
+/usr/bin/perl install-module.pl XMLRPC::Lite
122
+/usr/bin/perl install-module.pl Test::Taint
123
+/usr/bin/perl install-module.pl HTML::Scrubber
124
+/usr/bin/perl install-module.pl Email::Reply
125
+/usr/bin/perl install-module.pl HTML::FormatText::WithLinks
126
+/usr/bin/perl install-module.pl Cache::Memcached
112 127
BUGZILLAEOF
113 128
terminationCheck "$?"
114 129
read -n 1 -p "Bugzilla installation complete, when ready press a key to continue." key_pressed
docker/Dockerfile_sapsailing_on_sapmachine25
... ...
@@ -0,0 +1,25 @@
1
+FROM sapmachine:25.0.1
2
+# This Dockerfile assumes that the release to use is provided as
3
+# ${RELEASE}.tar.gz in the current Docker workspace. Use, e.g.,
4
+# the configuration/github-download-release-assets.sh script to
5
+# obtain the tar.gz file for a specific or the latest "main" release.
6
+ARG RELEASE
7
+LABEL maintainer=axel.uhl@sap.com
8
+LABEL org.opencontainers.image.description="Sailing Analytics with Java 25"
9
+# Download and extract the release
10
+WORKDIR /home/sailing/servers/server
11
+RUN apt-get update \
12
+ && apt-get install -y wget apt-utils vim telnet dnsutils net-tools jq
13
+COPY vimrc /root/.vimrc
14
+RUN wget -O /tmp/rds.pem https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem \
15
+ && ${JAVA_HOME}/bin/keytool -importcert -alias AWSRDS -file /tmp/rds.pem -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -storepass changeit \
16
+ && rm /tmp/rds.pem
17
+COPY ${RELEASE}.tar.gz /tmp
18
+RUN tar xzvpf /tmp/${RELEASE}.tar.gz \
19
+ && rm /tmp/${RELEASE}.tar.gz
20
+COPY env.sh .
21
+RUN cat env-default-rules.sh >>env.sh
22
+COPY start .
23
+COPY JavaSE-11.profile .
24
+EXPOSE 8888 14888 8000 7091 6666
25
+CMD [ "/home/sailing/servers/server/start", "docker" ]
docker/Dockerfile_windestimation-on-sapmachine25
... ...
@@ -0,0 +1,16 @@
1
+FROM sapmachine:25.0.1
2
+LABEL maintainer=axel.uhl@sap.com
3
+# Download and extract the release
4
+WORKDIR /home/sailing
5
+RUN chmod 777 /home/sailing; mkdir logs; mkdir dump
6
+RUN apt-get -y update; apt-get -y upgrade; apt-get -y install apt-utils wget curl
7
+RUN curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -
8
+RUN echo "deb [arch=amd64,arm64] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" >/etc/apt/sources.list.d/mongodb-org-4.4.list
9
+RUN apt-get update \
10
+ && apt-get install -y vim mongodb-org-shell
11
+COPY vimrc /root/.vimrc
12
+RUN wget -O /tmp/rds.pem https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem \
13
+ && ${JAVA_HOME}/bin/keytool -importcert -alias AWSRDS -file /tmp/rds.pem -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -storepass changeit \
14
+ && rm /tmp/rds.pem
15
+RUN wget -O /home/sailing/WindEstimationModelsTraining.jar https://static.sapsailing.com/WindEstimationModelsTraining.jar
16
+CMD exec java "${MEMORY}" -Dmongo.uri="${MONGODB_URI}" -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=dump/ -Xlog:gc*=info,gc+region*=info,gc+ergo*=info,gc+humongous*=info,gc+liveness=trace:file=logs/gc.log:time,level,tags:filecount=10,filesize=100000000 -jar WindEstimationModelsTraining.jar "${BEARER_TOKEN}" ${TRAINING_DATA_PERCENT}
docker/docker-compose-25.yml
... ...
@@ -0,0 +1,19 @@
1
+version: "3.10"
2
+services:
3
+ sailing-analytics:
4
+ image: "ghcr.io/sap/sailing-analytics:latest-25"
5
+ ports:
6
+ - "8888:8888"
7
+ - "6666:6666"
8
+ - "14888:14888"
9
+ environment:
10
+ MONGODB_URI: mongodb://mongo/test
11
+ SERVER_NAME: test
12
+ REPLICATION_HOST: rabbitmq
13
+ REPLICATION_CHANNEL: test
14
+ REPLICATION_PORT: 5672
15
+ mongo:
16
+ image: "mongo:7"
17
+ rabbitmq:
18
+ image: "rabbitmq:3.13"
19
+
docker/makeImageForLatestRelease-on-sapmachine25
... ...
@@ -0,0 +1,31 @@
1
+#!/bin/bash
2
+release_prefix=$1
3
+GITROOT="`dirname $0`/.."
4
+DOCKERDIR="${GITROOT}/docker"
5
+DOCKERFILE="$DOCKERDIR/Dockerfile"
6
+if [ "${release_prefix}" = "" ]; then
7
+ SET_LATEST=1
8
+ release_prefix="docker-25-"
9
+fi
10
+pushd "${DOCKERDIR}"
11
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
12
+if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13
+ echo "No release with prefix ${release_prefix} found" >&2
14
+else
15
+ release=$( echo ${RELEASE_TAR_GZ_FILENAME} | sed -e 's/\.tar\.gz//' )
16
+ echo Release is ${release}
17
+ echo "Copying files from GITROOT $GITROOT into Docker workspace"
18
+ cp "$GITROOT/java/target/env.sh" "$DOCKERDIR"
19
+ cp "$GITROOT/java/target/start" "$DOCKERDIR"
20
+ cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21
+ cd "$DOCKERDIR"
22
+ docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} -f Dockerfile_sapsailing_on_sapmachine25 .
23
+ echo "Cleaning up..."
24
+ rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
25
+ docker push ghcr.io/sap/sailing-analytics:${release}
26
+ if [ "$SET_LATEST" = "1" ]; then
27
+ docker tag ghcr.io/sap/sailing-analytics:${release} ghcr.io/sap/sailing-analytics:latest-25
28
+ docker push ghcr.io/sap/sailing-analytics:latest-25
29
+ fi
30
+fi
31
+popd
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/NOAADeclinationImportTest.java
... ...
@@ -1,9 +1,11 @@
1 1
package com.sap.sailing.declination.test;
2 2
3 3
import org.junit.jupiter.api.BeforeEach;
4
+import org.junit.jupiter.api.Disabled;
4 5
5 6
import com.sap.sailing.declination.impl.NOAAImporterForTesting;
6 7
8
+@Disabled("US Government Shutdown around 2025-10-01")
7 9
public class NOAADeclinationImportTest extends DeclinationImportTest<NOAAImporterForTesting> {
8 10
@BeforeEach
9 11
public void setUp() {
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/NOAADeclinationServiceTest.java
... ...
@@ -1,9 +1,11 @@
1 1
package com.sap.sailing.declination.test;
2 2
3 3
import org.junit.jupiter.api.BeforeEach;
4
+import org.junit.jupiter.api.Disabled;
4 5
5 6
import com.sap.sailing.declination.impl.NOAAImporter;
6 7
8
+@Disabled("US Government Shutdown around 2025-10-01")
7 9
public class NOAADeclinationServiceTest extends DeclinationServiceTest<NOAAImporter> {
8 10
@Override
9 11
@BeforeEach
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/NOAADeclinationStoreTest.java
... ...
@@ -1,9 +1,11 @@
1 1
package com.sap.sailing.declination.test;
2 2
3 3
import org.junit.jupiter.api.BeforeEach;
4
+import org.junit.jupiter.api.Disabled;
4 5
5 6
import com.sap.sailing.declination.impl.NOAAImporter;
6 7
8
+@Disabled("US Government Shutdown around 2025-10-01")
7 9
public class NOAADeclinationStoreTest extends DeclinationStoreTest<NOAAImporter> {
8 10
@Override
9 11
@BeforeEach
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/NOAASimpleDeclinationTest.java
... ...
@@ -1,9 +1,11 @@
1 1
package com.sap.sailing.declination.test;
2 2
3 3
import org.junit.jupiter.api.BeforeEach;
4
+import org.junit.jupiter.api.Disabled;
4 5
5 6
import com.sap.sailing.declination.impl.NOAAImporter;
6 7
8
+@Disabled("US Government Shutdown around 2025-10-01")
7 9
public class NOAASimpleDeclinationTest extends SimpleDeclinationTest<NOAAImporter> {
8 10
@BeforeEach
9 11
public void setUp() {
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCAM.java
... ...
@@ -56,9 +56,7 @@ public class EditCAM extends javax.swing.JDialog {
56 56
*/
57 57
58 58
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
59
- @SuppressWarnings("serial")
60 59
private void initComponents() {
61
-
62 60
jLabel1 = new javax.swing.JLabel();
63 61
jRaceID = new javax.swing.JTextField();
64 62
jSeparator1 = new javax.swing.JSeparator();
... ...
@@ -73,47 +71,35 @@ public class EditCAM extends javax.swing.JDialog {
73 71
jButton3 = new javax.swing.JButton();
74 72
jScrollPane1 = new javax.swing.JScrollPane();
75 73
jMarkList = new javax.swing.JList<ClockAtMarkElement>();
76
-
77 74
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
78
-
79 75
jLabel1.setText("Race ID:");
80
-
81 76
jRaceID.setText("jTextField1");
82
-
83 77
jLabel2.setText("Mark index:");
84
-
85 78
jMarkIndex.setText("0");
86
-
87 79
jLabel3.setText("Mark time:");
88
-
89 80
jMarkTime.setText("12:34:17");
90
-
91 81
jLabel4.setText("Sail number:");
92
-
93 82
jSailNumber.setText("w123");
94
-
95 83
jButton1.setText("Ok");
96 84
jButton1.addActionListener(new java.awt.event.ActionListener() {
97 85
public void actionPerformed(java.awt.event.ActionEvent evt) {
98 86
jButton1ActionPerformed(evt);
99 87
}
100 88
});
101
-
102 89
jButton2.setText("Remove");
103 90
jButton2.addActionListener(new java.awt.event.ActionListener() {
104 91
public void actionPerformed(java.awt.event.ActionEvent evt) {
105 92
jButton2ActionPerformed(evt);
106 93
}
107 94
});
108
-
109 95
jButton3.setText("Add");
110 96
jButton3.addActionListener(new java.awt.event.ActionListener() {
111 97
public void actionPerformed(java.awt.event.ActionEvent evt) {
112 98
jButton3ActionPerformed(evt);
113 99
}
114 100
});
115
-
116 101
jMarkList.setModel(new javax.swing.AbstractListModel<ClockAtMarkElement>() {
102
+ private static final long serialVersionUID = 1L;
117 103
ClockAtMarkElement[] clockAtMarkElements = { new ClockAtMarkElement(1, new Date(), "Item 1"),
118 104
new ClockAtMarkElement(1, new Date(), "Item 2"), new ClockAtMarkElement(1, new Date(), "Item 3"),
119 105
new ClockAtMarkElement(1, new Date(), "Item 4"), new ClockAtMarkElement(1, new Date(), "Item 5") };
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCCG.java
... ...
@@ -51,9 +51,7 @@ public class EditCCG extends javax.swing.JDialog {
51 51
*/
52 52
53 53
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
54
- @SuppressWarnings("serial")
55 54
private void initComponents() {
56
-
57 55
jBuoyType = new javax.swing.ButtonGroup();
58 56
jLabel1 = new javax.swing.JLabel();
59 57
jRaceID = new javax.swing.JTextField();
... ...
@@ -130,6 +128,7 @@ public class EditCCG extends javax.swing.JDialog {
130 128
});
131 129
132 130
jMarkList.setModel(new javax.swing.AbstractListModel<CCGMessage>() {
131
+ private static final long serialVersionUID = 1L;
133 132
CCGMessage[] ccgMessages = { new CCGMessage("Item 1", new ArrayList<Mark>()),
134 133
new CCGMessage("Item 2", new ArrayList<Mark>()),
135 134
new CCGMessage("Item 3", new ArrayList<Mark>()),
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditSTL.java
... ...
@@ -49,9 +49,7 @@ public class EditSTL extends javax.swing.JDialog {
49 49
*/
50 50
51 51
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
52
- @SuppressWarnings("serial")
53 52
private void initComponents() {
54
-
55 53
jLabel1 = new javax.swing.JLabel();
56 54
jRaceID = new javax.swing.JTextField();
57 55
jSeparator1 = new javax.swing.JSeparator();
... ...
@@ -66,48 +64,36 @@ public class EditSTL extends javax.swing.JDialog {
66 64
jButton2 = new javax.swing.JButton();
67 65
jScrollPane1 = new javax.swing.JScrollPane();
68 66
jCompetitorList = new javax.swing.JList<Competitor>();
69
-
70 67
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
71
-
72 68
jLabel1.setText("Race ID:");
73
-
74 69
jRaceID.setText("jTextField1");
75
-
76 70
jLabel2.setText("Sail Number:");
77
-
78 71
jSailNumber.setText("jTextField1");
79
-
80 72
jLabel3.setText("NOC:");
81
-
82 73
jNOC.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "AUS", "FRA", "GBR", "GER", "ITA", "USA" }));
83 74
jNOC.setSelectedIndex(2);
84
-
85 75
jLabel4.setText("Name:");
86
-
87 76
jName.setText("jTextField1");
88
-
89 77
jButton1.setText("Add");
90 78
jButton1.addActionListener(new java.awt.event.ActionListener() {
91 79
public void actionPerformed(java.awt.event.ActionEvent evt) {
92 80
jButton1ActionPerformed(evt);
93 81
}
94 82
});
95
-
96 83
jRemove.setText("Remove");
97 84
jRemove.addActionListener(new java.awt.event.ActionListener() {
98 85
public void actionPerformed(java.awt.event.ActionEvent evt) {
99 86
jRemoveActionPerformed(evt);
100 87
}
101 88
});
102
-
103 89
jButton2.setText("Ok");
104 90
jButton2.addActionListener(new java.awt.event.ActionListener() {
105 91
public void actionPerformed(java.awt.event.ActionEvent evt) {
106 92
jButton2ActionPerformed(evt);
107 93
}
108 94
});
109
-
110 95
jCompetitorList.setModel(new javax.swing.AbstractListModel<Competitor>() {
96
+ private static final long serialVersionUID = 1L;
111 97
Competitor[] competitors = { new CompetitorWithoutID("Item 1", "DEU", "Item 1"),
112 98
new CompetitorWithoutID("Item 2", "DEU", "Item 2"),
113 99
new CompetitorWithoutID("Item 3", "DEU", "Item 3"),
... ...
@@ -117,7 +103,6 @@ public class EditSTL extends javax.swing.JDialog {
117 103
public Competitor getElementAt(int i) { return competitors[i]; }
118 104
});
119 105
jScrollPane1.setViewportView(jCompetitorList);
120
-
121 106
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
122 107
getContentPane().setLayout(layout);
123 108
layout.setHorizontalGroup(
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditTMD.java
... ...
@@ -54,9 +54,7 @@ public class EditTMD extends javax.swing.JDialog {
54 54
*/
55 55
56 56
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
57
- @SuppressWarnings("serial")
58 57
private void initComponents() {
59
-
60 58
jLabel1 = new javax.swing.JLabel();
61 59
jRaceId = new javax.swing.JTextField();
62 60
jLabel2 = new javax.swing.JLabel();
... ...
@@ -73,51 +71,37 @@ public class EditTMD extends javax.swing.JDialog {
73 71
jButton3 = new javax.swing.JButton();
74 72
jScrollPane1 = new javax.swing.JScrollPane();
75 73
jTmdData = new javax.swing.JList<TimingDataElement>();
76
-
77 74
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
78
-
79 75
jLabel1.setText("Race ID:");
80
-
81 76
jRaceId.setText("jTextField1");
82
-
83 77
jLabel2.setText("Sail number:");
84
-
85 78
jSailNumber.setText("jTextField2");
86
-
87 79
jLabel3.setText("Mark index:");
88
-
89 80
jMarkIndex.setText("1");
90
-
91 81
jLabel4.setText("Rank:");
92
-
93 82
jRank.setText("1");
94
-
95 83
jLabel5.setText("Time since start:");
96
-
97 84
jTimeSinceStart.setText("00:12:14");
98
-
99 85
jButton1.setText("Remove");
100 86
jButton1.addActionListener(new java.awt.event.ActionListener() {
101 87
public void actionPerformed(java.awt.event.ActionEvent evt) {
102 88
jButton1ActionPerformed(evt);
103 89
}
104 90
});
105
-
106 91
jButton2.setText("Add");
107 92
jButton2.addActionListener(new java.awt.event.ActionListener() {
108 93
public void actionPerformed(java.awt.event.ActionEvent evt) {
109 94
jButton2ActionPerformed(evt);
110 95
}
111 96
});
112
-
113 97
jButton3.setText("Ok");
114 98
jButton3.addActionListener(new java.awt.event.ActionListener() {
115 99
public void actionPerformed(java.awt.event.ActionEvent evt) {
116 100
jButton3ActionPerformed(evt);
117 101
}
118 102
});
119
-
120 103
jTmdData.setModel(new javax.swing.AbstractListModel<TimingDataElement>() {
104
+ private static final long serialVersionUID = 1L;
121 105
TimingDataElement[] strings = { new TimingDataElement(1, 1, new Date()),
122 106
new TimingDataElement(2, 2, new Date()),
123 107
new TimingDataElement(3, 3, new Date()),
... ...
@@ -127,7 +111,6 @@ public class EditTMD extends javax.swing.JDialog {
127 111
public TimingDataElement getElementAt(int i) { return strings[i]; }
128 112
});
129 113
jScrollPane1.setViewportView(jTmdData);
130
-
131 114
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
132 115
getContentPane().setLayout(layout);
133 116
layout.setHorizontalGroup(
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/SwissTimingRaceEditor.java
... ...
@@ -70,10 +70,8 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
70 70
* WARNING: Do NOT modify this code. The content of this method is
71 71
* always regenerated by the Form Editor.
72 72
*/
73
- @SuppressWarnings({ "serial" })
74 73
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
75 74
private void initComponents() {
76
-
77 75
jScrollPane1 = new javax.swing.JScrollPane();
78 76
jCommandList = new javax.swing.JList<Object>();
79 77
jAdd = new javax.swing.JButton();
... ...
@@ -86,54 +84,46 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
86 84
jImport = new javax.swing.JMenuItem();
87 85
jExport = new javax.swing.JMenuItem();
88 86
jMenuItem3 = new javax.swing.JMenuItem();
89
-
90 87
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
91 88
setTitle("SwissTiming-Sample-Race Editor");
92
-
93 89
jCommandList.setModel(new javax.swing.AbstractListModel<Object>() {
90
+ private static final long serialVersionUID = 1L;
94 91
String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
95 92
public int getSize() { return strings.length; }
96 93
public Object getElementAt(int i) { return strings[i]; }
97 94
});
98 95
jScrollPane1.setViewportView(jCommandList);
99
-
100 96
jAdd.setText("Add");
101 97
jAdd.addActionListener(new java.awt.event.ActionListener() {
102 98
public void actionPerformed(java.awt.event.ActionEvent evt) {
103 99
jAddActionPerformed(evt);
104 100
}
105 101
});
106
-
107 102
jRemove.setText("Remove");
108 103
jRemove.addActionListener(new java.awt.event.ActionListener() {
109 104
public void actionPerformed(java.awt.event.ActionEvent evt) {
110 105
jRemoveActionPerformed(evt);
111 106
}
112 107
});
113
-
114 108
jEdit.setText("Edit");
115 109
jEdit.addActionListener(new java.awt.event.ActionListener() {
116 110
public void actionPerformed(java.awt.event.ActionEvent evt) {
117 111
jEditActionPerformed(evt);
118 112
}
119 113
});
120
-
121 114
jMoveUp.setText("Up");
122 115
jMoveUp.addActionListener(new java.awt.event.ActionListener() {
123 116
public void actionPerformed(java.awt.event.ActionEvent evt) {
124 117
jMoveUpActionPerformed(evt);
125 118
}
126 119
});
127
-
128 120
jMoveDown.setText("Down");
129 121
jMoveDown.addActionListener(new java.awt.event.ActionListener() {
130 122
public void actionPerformed(java.awt.event.ActionEvent evt) {
131 123
jMoveDownActionPerformed(evt);
132 124
}
133 125
});
134
-
135 126
jMenu1.setText("File");
136
-
137 127
jImport.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_I, java.awt.event.InputEvent.CTRL_DOWN_MASK));
138 128
jImport.setText("Import");
139 129
jImport.addActionListener(new java.awt.event.ActionListener() {
... ...
@@ -142,7 +132,6 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
142 132
}
143 133
});
144 134
jMenu1.add(jImport);
145
-
146 135
jExport.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_DOWN_MASK));
147 136
jExport.setText("Export");
148 137
jExport.addActionListener(new java.awt.event.ActionListener() {
... ...
@@ -151,7 +140,6 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
151 140
}
152 141
});
153 142
jMenu1.add(jExport);
154
-
155 143
jMenuItem3.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0));
156 144
jMenuItem3.setText("Exit");
157 145
jMenuItem3.addActionListener(new java.awt.event.ActionListener() {
... ...
@@ -160,11 +148,8 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
160 148
}
161 149
});
162 150
jMenu1.add(jMenuItem3);
163
-
164 151
jMenuBar1.add(jMenu1);
165
-
166 152
setJMenuBar(jMenuBar1);
167
-
168 153
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
169 154
getContentPane().setLayout(layout);
170 155
layout.setHorizontalGroup(
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java
... ...
@@ -156,7 +156,6 @@ public class TrackTest {
156 156
* {@link Timed} objects in ascending order, this method compares those results to the ordinary explicit calls
157 157
* to {@link GPSFixTrack#getEstimatedPosition(TimePoint, boolean)}.
158 158
*/
159
- @SuppressWarnings("serial")
160 159
@Test
161 160
public void testGetEstimatedPositionSingleVsIteratedWithSmallerSteps() {
162 161
TimePoint start = gpsFix1.getTimePoint().minus((gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())/2);
... ...
@@ -164,13 +163,15 @@ public class TrackTest {
164 163
List<Timed> timeds = new ArrayList<>();
165 164
for (TimePoint t = start; !t.after(end); t = t.plus((gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())/10)) {
166 165
final TimePoint finalT = t;
167
- timeds.add(new Timed() {public TimePoint getTimePoint() { return finalT; }});
166
+ timeds.add(new Timed() {
167
+ private static final long serialVersionUID = 7038806820707652754L;
168
+ public TimePoint getTimePoint() { return finalT; }
169
+ });
168 170
}
169 171
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ true);
170 172
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ false);
171 173
}
172 174
173
- @SuppressWarnings("serial")
174 175
@Test
175 176
public void testGetEstimatedPositionSingleVsIteratedWithLargerSteps() {
176 177
TimePoint start = gpsFix1.getTimePoint().minus((gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())/2);
... ...
@@ -178,7 +179,10 @@ public class TrackTest {
178 179
List<Timed> timeds = new ArrayList<>();
179 180
for (TimePoint t = start; !t.after(end); t = t.plus(gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())) {
180 181
final TimePoint finalT = t;
181
- timeds.add(new Timed() {public TimePoint getTimePoint() { return finalT; }});
182
+ timeds.add(new Timed() {
183
+ private static final long serialVersionUID = -6329517520161330872L;
184
+ public TimePoint getTimePoint() { return finalT; }
185
+ });
182 186
}
183 187
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ true);
184 188
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ false);
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java
... ...
@@ -589,7 +589,7 @@ public class MarkPassingCalculator {
589 589
// creation matches that of this mark passing calculator's race; load instead of compute
590 590
updateMarkPassingsFromRegistry();
591 591
queue.clear();
592
- stop(); // ensures an end marker is written to queue to the queue.take() call in Listen.run() will always get unblocked after the queue.clear() above
592
+ stop(); // ensures an end marker is written to queue so the queue.take() call in Listen.run() will always get unblocked after the queue.clear() above
593 593
suspended = false;
594 594
} else {
595 595
suspended = false;
java/com.sap.sailing.gwt.ui/Home.css
... ...
@@ -1 +1 @@
1
-/* Not used yet because all CssResources are comming from the design templates. */
1
+/* Not used yet because all CssResources are coming from the design templates. */
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.gss
... ...
@@ -1,6 +1,9 @@
1 1
@def SITEFOOTER_BACKGROUND_COLOR #333;
2 2
@def SITEFOOTER_TEXT_COLOR #fff;
3
-
3
+
4
+/**
5
+ * Mobile footer styling
6
+ */
4 7
.sitefooter {
5 8
font-size: 1rem;
6 9
background: SITEFOOTER_BACKGROUND_COLOR;
... ...
@@ -11,26 +14,22 @@
11 14
margin-top: -5.333333333333333em;
12 15
padding: 0 1.333333333333333em;
13 16
}
14
-.sitefooter a {
15
- color: SITEFOOTER_TEXT_COLOR;
16
- line-height: 2;
17
- padding: 5px;
18
-}
19
-.sitefooter_language {
20
- display: inline-block;
17
+.sitefooter_copyright {
18
+ font-weight: 600;
19
+ margin-right: 2em;
21 20
}
22
-.sitefooter_language select {
23
- background-color: SITEFOOTER_BACKGROUND_COLOR;
24
- color: SITEFOOTER_TEXT_COLOR;
21
+@media (min-width: 45em) {
22
+ .sitefooter_copyright {
23
+ float: left;
24
+ }
25 25
}
26 26
.sitefooter_links {
27
- display: flex;
28
- flex-wrap: wrap;
27
+ text-align: left;
29 28
font-size: 1em;
30 29
font-weight: 400;
31 30
}
32 31
.sitefooter_links_link {
33
- display: inline-block;
32
+ display: block;
34 33
color: SITEFOOTER_TEXT_COLOR;
35 34
text-decoration: none;
36 35
margin-right: 2em;
... ...
@@ -38,29 +37,87 @@
38 37
.sitefooter_links_link:last-child {
39 38
margin-right: 0;
40 39
}
41
-.sitefooter_copyright {
42
- font-weight: 600;
43
- margin-right: 2em;
40
+@media (min-width: 45em) {
41
+ .sitefooter_links_link {
42
+ display: inline-block;
43
+ }
44
+}
45
+.sitefooter_language {
46
+ text-align: left;
47
+}
48
+.sitefooter_language select {
49
+ background-color: SITEFOOTER_BACKGROUND_COLOR;
50
+}
51
+@media (min-width: 45em) {
52
+ .sitefooter_language {
53
+ float: right;
54
+ text-align: right;
55
+ }
44 56
}
45
-.sitefooter_row {
57
+/**
58
+ * Desktop footer styling
59
+ */
60
+.dfooter {
61
+ background: SITEFOOTER_BACKGROUND_COLOR;
62
+ color: SITEFOOTER_TEXT_COLOR;
63
+ font-size: 1rem;
64
+ border-top: 0.333333333333333em solid SITEFOOTER_BACKGROUND_COLOR;
65
+ padding: 0 1.333333333333333em;
66
+ margin-top: -5.333333333333333em;
67
+ display: block;
68
+ line-height: normal;
69
+ width: 100%;
70
+ position: relative;
71
+ z-index: 1000;
72
+}
73
+.dfooter_row {
46 74
display: flex;
47
- align-items: center;
48
- justify-content: space-between;
49 75
flex-wrap: wrap;
76
+ align-items: center;
77
+ min-height: 5em;
78
+ row-gap: 0.25em;
79
+ column-gap: 2em;
50 80
}
51
-.sitefooter_left {
81
+.dfooter_left {
52 82
display: flex;
53
- align-items: center;
54 83
flex-wrap: wrap;
84
+ align-items: baseline;
85
+ /*change rem here to configure preferred width before right block drops */
86
+ flex: 1 1 14rem;
87
+ row-gap: 0.25em;
88
+ column-gap: 2em;
89
+ min-width: 0;
55 90
}
56
-.sitefooter_right {
91
+.dfooter_right {
92
+ display: flex;
93
+ align-items: center;
57 94
margin-left: auto;
58
- text-align: right;
95
+ flex: 0 0 auto;
96
+ min-width: 8rem;
59 97
}
60
-@media (max-width: 45em) {
61
- .sitefooter_right {
62
- width: 100%;
63
- text-align: right;
64
- margin-left: 0;
65
- }
98
+.dfooter_item,
99
+.dfooter_link,
100
+.dfooter_copyright {
101
+ line-height: 1;
102
+ margin: 0;
103
+ padding: 0;
104
+ display: inline-flex;
105
+ align-items: center;
106
+}
107
+.dfooter_copyright {
108
+ font-weight: 600;
109
+ white-space: nowrap;
110
+ color: SITEFOOTER_TEXT_COLOR;
111
+}
112
+.dfooter_link {
113
+ text-decoration: none;
114
+ color: SITEFOOTER_TEXT_COLOR;
115
+}
116
+.dfooter_link:hover,
117
+.dfooter_link:focus { text-decoration: underline; }
118
+.dfooter_language { text-align: right; white-space: nowrap; }
119
+.dfooter_language select {
120
+ background-color: SITEFOOTER_BACKGROUND_COLOR;
121
+ color: SITEFOOTER_TEXT_COLOR;
122
+ max-width: 100%;
66 123
}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.gwt.home.desktop.partials.footer;
2 2
3
+import static com.google.gwt.dom.client.Style.Display.NONE;
3 4
import static com.sap.sse.gwt.shared.DebugConstants.DEBUG_ID_ATTRIBUTE;
4 5
5 6
import java.util.Optional;
... ...
@@ -25,6 +26,7 @@ import com.sap.sse.gwt.shared.DebugConstants;
25 26
26 27
public class Footer extends Composite {
27 28
private static FooterPanelUiBinder uiBinder = GWT.create(FooterPanelUiBinder.class);
29
+ private ClientConfiguration cfg = ClientConfiguration.getInstance();
28 30
29 31
interface FooterPanelUiBinder extends UiBinder<Widget, Footer> {
30 32
}
... ...
@@ -36,7 +38,7 @@ public class Footer extends Composite {
36 38
@UiField AnchorElement imprintAnchorLink;
37 39
@UiField AnchorElement privacyAnchorLink;
38 40
@UiField AnchorElement mobileUi;
39
- @UiField AnchorElement sapJobsAnchor;
41
+ @UiField AnchorElement jobsAnchor;
40 42
41 43
public Footer(EventBus eventBus) {
42 44
FooterResources.INSTANCE.css().ensureInjected();
... ...
@@ -53,19 +55,18 @@ public class Footer extends Composite {
53 55
}
54 56
}
55 57
});
56
- ClientConfiguration cfg = ClientConfiguration.getInstance();
57
- if (!cfg.isBrandingActive()) {
58
- copyrightDiv.getStyle().setDisplay(Display.NONE);
58
+ if (!ClientConfiguration.getInstance().isBrandingActive()) {
59
+ copyrightDiv.getStyle().setDisplay(NONE);
59 60
languageSelector.setLabelText(StringMessages.INSTANCE.whitelabelFooterLanguage());
60 61
supportAnchor.getStyle().setDisplay(Display.NONE);
61 62
whatsNewAnchor.getStyle().setDisplay(Display.NONE);
62 63
imprintAnchorLink.getStyle().setDisplay(Display.NONE);
63 64
privacyAnchorLink.getStyle().setDisplay(Display.NONE);
64
- sapJobsAnchor.getStyle().setDisplay(Display.NONE);
65
+ jobsAnchor.getStyle().setDisplay(Display.NONE);
65 66
} else {
66 67
hideIfBlank(copyrightDiv, cfg.getFooterCopyright());
67 68
setHrefOrHide(privacyAnchorLink, cfg.getFooterPrivacyLink());
68
- setHrefOrHide(sapJobsAnchor, cfg.getFooterJobsLink());
69
+ setHrefOrHide(jobsAnchor, cfg.getFooterJobsLink());
69 70
setHrefOrHide(supportAnchor, cfg.getFooterSupportLink());
70 71
setHrefOrHide(whatsNewAnchor, cfg.getFooterWhatsNewLink());
71 72
setHrefOrHide(imprintAnchorLink, cfg.getFooterLegalLink());
... ...
@@ -80,20 +81,23 @@ public class Footer extends Composite {
80 81
imprintAnchorLink.setAttribute(DEBUG_ID_ATTRIBUTE, "imprintAnchorLink");
81 82
privacyAnchorLink.setAttribute(DEBUG_ID_ATTRIBUTE, "privacyAnchorLink");
82 83
languageSelector.getElement().setAttribute(DEBUG_ID_ATTRIBUTE, "languageSelector");
83
- sapJobsAnchor.setAttribute(DEBUG_ID_ATTRIBUTE, "sapJobsAnchor");
84 84
}
85
+
85 86
private static boolean hideIfBlank(DivElement el, String text) {
87
+ boolean flag = false;
86 88
if (!Util.hasLength(text)) {
87 89
el.getStyle().setDisplay(Display.NONE);
88
- return true;
90
+ flag = true;
89 91
}
90
- return false;
92
+ return flag;
91 93
}
94
+
92 95
private static void setHrefOrHide(AnchorElement el, String url) {
93 96
if (!Util.hasLength(url)) {
94 97
el.getStyle().setDisplay(Display.NONE);
95
- } else {
98
+ } else if (!url.equals("nothing")) {
96 99
el.setHref(url);
97 100
}
98 101
}
99
-}
102
+
103
+}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml
... ...
@@ -5,37 +5,35 @@
5 5
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
6 6
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
7 7
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.footer.FooterResources" />
8
- <g:HTMLPanel tag="footer" addStyleNames="{local_res.css.sitefooter}">
8
+ <g:HTMLPanel tag="footer" addStyleNames="{local_res.css.dfooter}">
9 9
<!-- Website footer-->
10
- <div class="{res.mediaCss.grid} {local_res.css.sitefooter_row}">
11
- <div class="{res.mediaCss.small12} {res.mediaCss.medium9} {res.mediaCss.columns} {local_res.css.sitefooter_left}">
12
- <div ui:field="copyrightDiv" class="{local_res.css.sitefooter_copyright}">
13
- </div>
14
- <div class="{local_res.css.sitefooter_links}">
15
- <a ui:field="imprintAnchorLink" class="{local_res.css.sitefooter_links_link}">
10
+ <div class="{res.mediaCss.grid} {local_res.css.dfooter_row}">
11
+ <div class="{local_res.css.dfooter_left}">
12
+ <div ui:field="copyrightDiv" class="{local_res.css.dfooter_item} {local_res.css.dfooter_copyright}">
13
+ </div>
14
+ <a ui:field="imprintAnchorLink" target="_blank" class="{local_res.css.dfooter_item} {local_res.css.dfooter_link}">
16 15
<ui:text from='{i18n.footerLegal}' />
17 16
</a>
18
- <a ui:field="privacyAnchorLink" target="_blank" class="{local_res.css.sitefooter_links_link}">
17
+ <a ui:field="privacyAnchorLink" target="_blank" class="{local_res.css.dfooter_item} {local_res.css.dfooter_link}">
19 18
<ui:text from='{i18n.footerPrivacy}' />
20 19
</a>
21
- <a ui:field="sapJobsAnchor" target="_blank" class="{local_res.css.sitefooter_links_link}">
20
+ <a ui:field="jobsAnchor" target="_blank" class="{local_res.css.dfooter_item} {local_res.css.dfooter_link}">
22 21
<ui:text from='{i18n.footerJobs}'/>
23 22
</a>
24
- <a ui:field="supportAnchor" target="_blank" class="{local_res.css.sitefooter_links_link}">
23
+ <a ui:field="supportAnchor" class="{local_res.css.dfooter_item} {local_res.css.dfooter_link}">
25 24
<ui:text from='{i18n.footerSupport}' />
26 25
</a>
26
+ <a ui:field="whatsNewAnchor" class="{local_res.css.dfooter_item} {local_res.css.dfooter_link}">
27
+ <ui:text from='{i18n.whatsNew}' />
28
+ </a>
27 29
<a ui:field="mobileUi" title="{i18n.mobile}" href=""
28
- class="{local_res.css.sitefooter_links_link} {res.mediaCss.showonsmall} {res.mediaCss.hideonmedium} {res.mediaCss.hideonlarge}">
30
+ class="{local_res.css.dfooter_item} {local_res.css.dfooter_link} {res.mediaCss.showonsmall} {res.mediaCss.hideonmedium} {res.mediaCss.hideonlarge}">
29 31
<ui:text from='{i18n.mobile}' />
30 32
</a>
31
- <a ui:field="whatsNewAnchor" class="{local_res.css.sitefooter_links_link}">
32
- <ui:text from='{i18n.whatsNew}' />
33
- </a>
34 33
</div>
34
+ <div class="{local_res.css.dfooter_right}">
35
+ <l:LanguageSelector ui:field="languageSelector" addStyleNames="{local_res.css.dfooter_language}" />
35 36
</div>
36
- <div class="{res.mediaCss.small12} {res.mediaCss.medium3} {res.mediaCss.columns} {local_res.css.sitefooter_right}">
37
- <l:LanguageSelector ui:field="languageSelector" addStyleNames="{local_res.css.sitefooter_language}" />
38
- </div>
39
- </div>
37
+ </div>
40 38
</g:HTMLPanel>
41
-</ui:UiBinder>
39
+</ui:UiBinder>
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/FooterResources.java
... ...
@@ -16,8 +16,13 @@ public interface FooterResources extends ClientBundle {
16 16
String sitefooter_links();
17 17
String sitefooter_links_link();
18 18
String sitefooter_language();
19
- String sitefooter_row();
20
- String sitefooter_left();
21
- String sitefooter_right();
19
+ String dfooter();
20
+ String dfooter_row();
21
+ String dfooter_left();
22
+ String dfooter_right();
23
+ String dfooter_item();
24
+ String dfooter_copyright();
25
+ String dfooter_link();
26
+ String dfooter_language();
22 27
}
23
-}
28
+}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/events/TabletAndDesktopEventsView.java
... ...
@@ -14,6 +14,7 @@ import com.sap.sailing.gwt.home.shared.places.start.StartPlace;
14 14
import com.sap.sailing.gwt.ui.client.StringMessages;
15 15
import com.sap.sse.gwt.client.breadcrumb.BreadcrumbPane;
16 16
import com.sap.sse.gwt.client.media.TakedownNoticeService;
17
+import com.sap.sse.gwt.resources.CommonControlsCSS;
17 18
18 19
public class TabletAndDesktopEventsView extends AbstractEventsView {
19 20
private static EventsPageViewUiBinder uiBinder = GWT.create(EventsPageViewUiBinder.class);
... ...
@@ -33,9 +34,7 @@ public class TabletAndDesktopEventsView extends AbstractEventsView {
33 34
this.navigator = navigator;
34 35
recentEventsWidget = new EventsOverviewRecent(navigator);
35 36
upcomingEventsWidget = new EventsOverviewUpcoming(navigator);
36
-
37 37
initWidget(uiBinder.createAndBindUi(this));
38
-
39 38
initBreadCrumbs();
40 39
}
41 40
... ...
@@ -57,6 +56,12 @@ public class TabletAndDesktopEventsView extends AbstractEventsView {
57 56
});
58 57
}
59 58
59
+ @Override
60
+ protected void onLoad() {
61
+ super.onLoad();
62
+ CommonControlsCSS.ensureInjected();
63
+ }
64
+
60 65
@Override
61 66
protected void updateEventsUI(TakedownNoticeService takedownNoticeService) {
62 67
recentEventsWidget.updateEvents(eventListView.getRecentEvents(), takedownNoticeService);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
... ...
@@ -13,6 +13,10 @@
13 13
<li>Bug fix: when a split-fleet series had results only entered manually as score
14 14
corrections, the progress bars for the series were not shown properly. All
15 15
fleets would be considered finished although some may not have seen results.</li>
16
+ <li>The source code of the Sailing Analytics including its companion apps (Race Manager App,
17
+ Buoy Pinger App, Sail Insight) has been published under the Apache 2.0 license on
18
+ GitHub. See <a href="https://github.com/SAP/sailing-analytics">https://github.com/SAP/sailing-analytics</a>.
19
+ Get engaged!</li>
16 20
</ul>
17 21
<h5 class="articleSubheadline">September 2025</h5>
18 22
<ul class="bulletList">
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/MobileEntryPoint.java
... ...
@@ -35,12 +35,9 @@ public class MobileEntryPoint extends AbstractMvpEntryPoint<StringMessages, Mobi
35 35
@Override
36 36
public void doOnModuleLoad() {
37 37
Document.get().getBody().addClassName(SharedResources.INSTANCE.mainCss().mobile());
38
-
39 38
CommonControlsCSS.ensureInjected();
40
-
41 39
ServerConfigurationServiceAsync serverConfigService = GWT.create(ServerConfigurationService.class);
42 40
EntryPointHelper.registerASyncService((ServiceDefTarget) serverConfigService, RemoteServiceMappingConstants.serverConfigurationServiceRemotePath);
43
-
44 41
serverConfigService.isStandaloneServer(new AsyncCallback<Boolean>() {
45 42
@Override
46 43
public void onSuccess(Boolean result) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.java
... ...
@@ -7,6 +7,7 @@ import java.util.Optional;
7 7
import com.google.gwt.core.client.GWT;
8 8
import com.google.gwt.dom.client.AnchorElement;
9 9
import com.google.gwt.dom.client.DivElement;
10
+import com.google.gwt.dom.client.SpanElement;
10 11
import com.google.gwt.dom.client.Style.Display;
11 12
import com.google.gwt.uibinder.client.UiBinder;
12 13
import com.google.gwt.uibinder.client.UiField;
... ...
@@ -27,7 +28,8 @@ import com.sap.sse.gwt.shared.ClientConfiguration;
27 28
*/
28 29
public class Footer extends Composite {
29 30
private static FooterPanelUiBinder uiBinder = GWT.create(FooterPanelUiBinder.class);
30
- ClientConfiguration cfg = ClientConfiguration.getInstance();
31
+
32
+ private ClientConfiguration cfg = ClientConfiguration.getInstance();
31 33
32 34
interface FooterPanelUiBinder extends UiBinder<Widget, Footer> {
33 35
}
... ...
@@ -39,7 +41,8 @@ public class Footer extends Composite {
39 41
@UiField AnchorElement imprintAnchorLink;
40 42
@UiField AnchorElement desktopUi;
41 43
@UiField AnchorElement jobsAnchor;
42
- @UiField AnchorElement privacyAnchorLink;
44
+ @UiField AnchorElement privacyAnchor;
45
+ @UiField SpanElement pipe;
43 46
44 47
public Footer() {
45 48
FooterResources.INSTANCE.css().ensureInjected();
... ...
@@ -54,17 +57,21 @@ public class Footer extends Composite {
54 57
}
55 58
}
56 59
});
57
- if (!cfg.isBrandingActive()) {
60
+ if (!ClientConfiguration.getInstance().isBrandingActive()) {
58 61
copyrightDiv.getStyle().setDisplay(NONE);
59 62
languageSelector.setLabelText(StringMessages.INSTANCE.whitelabelFooterLanguage());
60 63
supportAnchor.getStyle().setDisplay(Display.NONE);
61 64
whatsNewLinkUi.getElement().getStyle().setDisplay(Display.NONE);
62 65
imprintAnchorLink.getStyle().setDisplay(Display.NONE);
63 66
jobsAnchor.getStyle().setDisplay(Display.NONE);
64
- privacyAnchorLink.getStyle().setDisplay(NONE);
67
+ privacyAnchor.getStyle().setDisplay(Display.NONE);
65 68
} else {
69
+ pipe.setInnerText("|");
70
+ if (!hideIfBlank(copyrightDiv, cfg.getFooterCopyright())) {
71
+ copyrightDiv.setInnerText(cfg.getFooterCopyright());
72
+ }
66 73
languageSelector.setLabelText(cfg.getBrandTitle(Optional.empty()) + " " + StringMessages.INSTANCE.whitelabelFooterLanguage());
67
- setHrefOrHide(privacyAnchorLink, cfg.getFooterPrivacyLink());
74
+ setHrefOrHide(privacyAnchor, cfg.getFooterPrivacyLink());
68 75
setHrefOrHide(jobsAnchor, cfg.getFooterJobsLink());
69 76
setHrefOrHide(supportAnchor, cfg.getFooterSupportLink());
70 77
setHrefOrHide(imprintAnchorLink, cfg.getFooterLegalLink());
... ...
@@ -80,21 +87,19 @@ public class Footer extends Composite {
80 87
}
81 88
82 89
private static boolean hideIfBlank(DivElement el, String text) {
83
- final boolean result;
84 90
if (!Util.hasLength(text)) {
85 91
el.getStyle().setDisplay(Display.NONE);
86
- result = true;
87
- } else {
88
- result = false;
92
+ return true;
89 93
}
90
- return result;
94
+ return false;
91 95
}
92 96
93 97
private static void setHrefOrHide(AnchorElement el, String url) {
94 98
if (!Util.hasLength(url)) {
95
- el.getStyle().setDisplay(Display.NONE);
99
+ el.getStyle().setDisplay(Display.NONE);
96 100
} else {
97
- el.setHref(url);
101
+ el.setHref(url);
98 102
}
99 103
}
100
-}
104
+
105
+}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.ui.xml
... ...
@@ -9,35 +9,34 @@
9 9
<!-- Footer mobile -->
10 10
<footer class="{local_res.css.sitefooter}">
11 11
<div class="{res.mediaCss.grid}">
12
- <l:LanguageSelector ui:field="languageSelector"
13
- addStyleNames="{local_res.css.sitefooter_language} {res.mediaCss.small12} {res.mediaCss.columns}"/>
12
+ <div ui:field="copyrightDiv" class="{res.mediaCss.small12} {res.mediaCss.columns} {local_res.css.sitefooter_copyright}">
13
+ </div>
14 14
<div class="{local_res.css.sitefooter_links} {res.mediaCss.small12} {res.mediaCss.columns}">
15 15
<a ui:field="imprintAnchorLink">
16 16
<ui:text from='{i18n.footerLegal}' />
17 17
</a>
18
- <a ui:field="privacyAnchorLink"
18
+ <a ui:field="privacyAnchor"
19 19
title="{i18n.footerPrivacy}">
20 20
<ui:text from='{i18n.footerPrivacy}' />
21 21
</a>
22
- <a ui:field="jobsAnchor">
22
+ <a ui:field="jobsAnchor" target="_blank">
23 23
<ui:text from='{i18n.footerJobs}'/>
24 24
</a>
25 25
<br></br>
26
- <a ui:field="supportAnchor"
26
+ <a ui:field="supportAnchor" href=""
27 27
title="{i18n.footerSupport}">
28 28
<ui:text from='{i18n.footerSupport}' />
29 29
</a>
30
- |
31
- <a ui:field="desktopUi" title="{i18n.desktop}" href="">
32
- <ui:text from='{i18n.desktop}' />
33
- </a>
34
- <br></br>
35 30
<g:Anchor title="{i18n.whatsNew}" ui:field="whatsNewLinkUi">
36 31
<ui:text from='{i18n.whatsNew}' />
37 32
</g:Anchor>
33
+ <span ui:field="pipe"></span>
34
+ <a ui:field="desktopUi" title="{i18n.desktop}" href="">
35
+ <ui:text from='{i18n.desktop}' />
36
+ </a>
38 37
</div>
39
- <div ui:field="copyrightDiv" class="{res.mediaCss.small12} {res.mediaCss.columns} {local_res.css.sitefooter_copyright}">
40
- </div>
38
+ <l:LanguageSelector ui:field="languageSelector"
39
+ addStyleNames="{local_res.css.sitefooter_language} {res.mediaCss.small12} {res.mediaCss.columns}"/>
41 40
</div>
42 41
</footer>
43 42
</g:HTMLPanel>
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_cs.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=Chyba při kopírování propojení: {0}
2573 2573
successfullyCopiedPairings=Úspěšně zkopírovaná propojení
2574 2574
selectFromRaceColumn=Vyberte sloupec rozjížďky, od kterého chcete zahájit kopírování propojení
2575 2575
selectToRaceColumn=Vyberte sloupec rozjížďky, do kterého chcete zkopírovat propojení
2576
+exportTWAHistogramToCsv=Export histogramu úhlu skutečného větru do CSV
2577
+exportWindSpeedHistogramToCsv=Export histogramu rychlosti větru do CSV
2578
+optionalBearerTokenForWindImport=Volitelný nosný token pro import větru
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_da.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=Fejl ved kopiering af par: {0}
2573 2573
successfullyCopiedPairings=Par blev kopieret
2574 2574
selectFromRaceColumn=Vælg kapsejladskolonne, hvorfra kopiering af par skal startes
2575 2575
selectToRaceColumn=Vælg kapsejladskolonne, hvortil kopiering af par skal startes
2576
+exportTWAHistogramToCsv=Eksporter histogram for sand vindvinkel til CSV
2577
+exportWindSpeedHistogramToCsv=Eksporter histogram for vindhastighed til CSV
2578
+optionalBearerTokenForWindImport=Valgfrit ihændehavertoken til vindimport
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=Error al copiar emparejamientos: {0}
2573 2573
successfullyCopiedPairings=Emparejamientos copiados correctamente
2574 2574
selectFromRaceColumn=Seleccione la columna Prueba desde la que se inicia la copia de emparejamientos
2575 2575
selectToRaceColumn=Seleccione la columna Prueba desde la que se copian los emparejamientos
2576
+exportTWAHistogramToCsv=Exportar histograma de ángulos de viento reales a CSV
2577
+exportWindSpeedHistogramToCsv=Exportar histograma de velocidades de viento a CSV
2578
+optionalBearerTokenForWindImport=Token de portador opcional para importación de viento
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=Erreur lors de la copie des appariements : {0}
2573 2573
successfullyCopiedPairings=Appariements copiés
2574 2574
selectFromRaceColumn=Sélectionnez la colonne de la course dans laquelle commencer à copier des appariements.
2575 2575
selectToRaceColumn=Sélectionnez la colonne de la course dans laquelle commencer à coller des appariements.
2576
+exportTWAHistogramToCsv=Exporter l''histogramme de l''angle du vent réel au format CSV
2577
+exportWindSpeedHistogramToCsv=Exporter l''histogramme de la vitesse du vent au format CSV
2578
+optionalBearerTokenForWindImport=Jeton porteur facultatif pour l''importation des données de vent
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_it.properties
... ...
@@ -2574,3 +2574,6 @@ errorCopyingPairings=Errore di copia degli accoppiamenti: {0}
2574 2574
successfullyCopiedPairings=Accoppiamenti copiati correttamente
2575 2575
selectFromRaceColumn=Seleziona la colonna della gara da cui iniziare a copiare gli accoppiamenti
2576 2576
selectToRaceColumn=Seleziona la colonna della gara in cui copiare gli accoppiamenti
2577
+exportTWAHistogramToCsv=Esporta istogramma angolo del vento reale in CSV
2578
+exportWindSpeedHistogramToCsv=Esporta istogramma velocità del vento in CSV
2579
+optionalBearerTokenForWindImport=Bearer token facoltativo per l’importazione vento
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=対戦組み合わせのコピーでエラーが発生: {0}
2573 2573
successfullyCopiedPairings=対戦組み合わせを正常にコピー済み
2574 2574
selectFromRaceColumn=対戦組み合わせのコピーを開始する元のレース列を選択
2575 2575
selectToRaceColumn=対戦組み合わせのコピー先のレース列を選択
2576
+exportTWAHistogramToCsv=真風角度ヒストグラムの CSV へのエクスポート
2577
+exportWindSpeedHistogramToCsv=風速ヒストグラムの CSV へのエクスポート
2578
+optionalBearerTokenForWindImport=風インポートの任意ベアラートークン
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=Erro ao copiar pares: {0}
2573 2573
successfullyCopiedPairings=Pares copiados com êxito
2574 2574
selectFromRaceColumn=Selecionar coluna da corrida a partir da qual deve ser iniciada a cópia dos pares
2575 2575
selectToRaceColumn=Selecionar coluna da corrida para a qual deve ser efetuada a cópia dos pares
2576
+exportTWAHistogramToCsv=Exportar histograma do ângulo do vento verdadeiro para CSV
2577
+exportWindSpeedHistogramToCsv=Exportar histograma da velocidade do vento para CSV
2578
+optionalBearerTokenForWindImport=Token de portador opcional para importação do vento
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=Ошибка при копировании пар: {0}
2573 2573
successfullyCopiedPairings=Успешно скопированные пары
2574 2574
selectFromRaceColumn=Выберите столбец гонки, с которого требуется начать копирование пар
2575 2575
selectToRaceColumn=Выберите столбец гонки, в который требуется копировать пары
2576
+exportTWAHistogramToCsv=Экспортировать гистограмму угла истинного ветра в CSV
2577
+exportWindSpeedHistogramToCsv=Экспортировать гистограмму скорости ветра в CSV
2578
+optionalBearerTokenForWindImport=Опциональный маркер носителя для импорта ветра
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_sl.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=Napaka pri kopiranju parov: {0}
2573 2573
successfullyCopiedPairings=Uspešno kopirani pari
2574 2574
selectFromRaceColumn=Izberite stolpec plova, iz katerega želite začeti kopirati pare
2575 2575
selectToRaceColumn=Izberite stolpec plova, v katerega želite začeti kopirati pare
2576
+exportTWAHistogramToCsv=Izvoz histograma kota pravega vetra v CSV
2577
+exportWindSpeedHistogramToCsv=Izvoz histograma hitrosti vetra v CSV
2578
+optionalBearerTokenForWindImport=Neobvezni nosilni žeton za uvoz vetra
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
... ...
@@ -2573,3 +2573,6 @@ errorCopyingPairings=复制配对出错:{0}
2573 2573
successfullyCopiedPairings=已成功复制配对
2574 2574
selectFromRaceColumn=选择要从哪一个比赛轮次列开始复制配对
2575 2575
selectToRaceColumn=选择要将配对复制到哪一个比赛轮次列
2576
+exportTWAHistogramToCsv=将实际风向角直方图导出到 CSV
2577
+exportWindSpeedHistogramToCsv=将风速直方图导出到 CSV
2578
+optionalBearerTokenForWindImport=用于风力数据导入的可选持有者令牌
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/WindowManager.java
... ...
@@ -5,6 +5,7 @@ import java.util.Set;
5 5
import java.util.function.BiConsumer;
6 6
import java.util.function.Consumer;
7 7
import java.util.function.Supplier;
8
+import java.util.logging.Logger;
8 9
9 10
import org.openqa.selenium.Dimension;
10 11
import org.openqa.selenium.JavascriptExecutor;
... ...
@@ -21,6 +22,8 @@ import org.openqa.selenium.support.ui.WebDriverWait;
21 22
* Riccardo Nimser (D049941)
22 23
*/
23 24
public class WindowManager {
25
+ private static final Logger logger = Logger.getLogger(WindowManager.class.getName());
26
+
24 27
private WebDriverWindow defaultWindow;
25 28
private final Set<WebDriverWindow> allWindows = new HashSet<>();
26 29
private WebDriver driver;
... ...
@@ -108,12 +111,18 @@ public class WindowManager {
108 111
}
109 112
110 113
private boolean isDriverAlive(WebDriver driver) {
111
- try {
112
- driver.getWindowHandles();
113
- return true;
114
- } catch (NoSuchSessionException | SessionNotCreatedException e) {
115
- return false;
114
+ boolean result;
115
+ if (driver == null) {
116
+ result = false;
117
+ } else {
118
+ try {
119
+ driver.getWindowHandles();
120
+ result = true;
121
+ } catch (NoSuchSessionException | SessionNotCreatedException e) {
122
+ result = false;
123
+ }
116 124
}
125
+ return result;
117 126
}
118 127
119 128
private void setWindowMaximized(WebDriver driver) {
... ...
@@ -137,6 +146,16 @@ public class WindowManager {
137 146
138 147
public void closeAllWindows() {
139 148
forEachOpenedWindow(WebDriverWindow::close);
149
+ if (driver != null) {
150
+ try {
151
+ driver.close();
152
+ driver.quit();
153
+ } catch (org.openqa.selenium.NoSuchSessionException e) {
154
+ logger.warning("The Selenium driver seems to have already been closed");
155
+ // Already closed — ignore
156
+ }
157
+ driver = null;
158
+ }
140 159
}
141 160
142 161
private class ManagedWebDriverWindow extends WebDriverWindow {
java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/DynamicTrackedRaceWithMarkPassingCalculator.java
... ...
@@ -0,0 +1,28 @@
1
+package com.sap.sailing.server.trackfiles.test;
2
+
3
+import com.sap.sailing.domain.base.RaceDefinition;
4
+import com.sap.sailing.domain.base.Sideline;
5
+import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
6
+import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
7
+import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
8
+import com.sap.sailing.domain.ranking.RankingMetricConstructor;
9
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
10
+import com.sap.sailing.domain.tracking.TrackedRegatta;
11
+import com.sap.sailing.domain.tracking.WindStore;
12
+import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
13
+
14
+public class DynamicTrackedRaceWithMarkPassingCalculator extends DynamicTrackedRaceImpl {
15
+ private static final long serialVersionUID = -8076705893930566222L;
16
+
17
+ public DynamicTrackedRaceWithMarkPassingCalculator(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable<Sideline> sidelines,
18
+ WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
19
+ long millisecondsOverWhichToAverageSpeed, boolean useInternalMarkPassingAlgorithm,
20
+ RankingMetricConstructor rankingMetricConstructor, RaceLogAndTrackedRaceResolver raceLogResolver, TrackingConnectorInfo trackingConnectorInfo,
21
+ MarkPassingRaceFingerprintRegistry markPassingRaceFingerprintRegistry) {
22
+ super(trackedRegatta, race, sidelines, windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, useInternalMarkPassingAlgorithm, rankingMetricConstructor, raceLogResolver, trackingConnectorInfo, markPassingRaceFingerprintRegistry);
23
+ }
24
+
25
+ public MarkPassingCalculator getMarkPassingCalculator() {
26
+ return markPassingCalculator;
27
+ }
28
+}
java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/JumpyTrackSmootheningTest.java
... ...
@@ -68,7 +68,6 @@ import com.sap.sailing.domain.tracking.MarkPassing;
68 68
import com.sap.sailing.domain.tracking.TrackedRegatta;
69 69
import com.sap.sailing.domain.tracking.WindTrack;
70 70
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
71
-import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
72 71
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
73 72
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
74 73
import com.sap.sailing.domain.tracking.impl.OutlierFilter;
... ...
@@ -197,10 +196,11 @@ public class JumpyTrackSmootheningTest {
197 196
durationForOriginalTrack = startedAt.until(doneAt);
198 197
logger.info("Duration for computing mark passings with original track: "+durationForOriginalTrack);
199 198
assertNotNull(markPassings);
200
- assertEquals(13, markPassings.size());
199
+ assertEquals(13, markPassings.size()); // TODO this is brittle... on some machines this results in only five mark passings
201 200
}
202
- assertTrue(durationForAdjustedTrack.times(8).compareTo(durationForOriginalTrack) < 0,
203
- "Expected duration for mark passing analysis on adjusted track to be at least eight times less than for original track");
201
+ assertTrue(durationForAdjustedTrack.times(2).compareTo(durationForOriginalTrack) < 0,
202
+ "Expected duration for mark passing analysis on adjusted track to be at least two times less than for original track: "+
203
+ durationForAdjustedTrack+" vs. "+durationForOriginalTrack);
204 204
}
205 205
206 206
private DynamicGPSFixTrack<Competitor, GPSFixMoving> readTrack(String filename) throws Exception {
... ...
@@ -227,7 +227,8 @@ public class JumpyTrackSmootheningTest {
227 227
}
228 228
229 229
/**
230
- * Simulates the "Oak cliff DH Distance Race" R1 with a single competitor, Gallagher / Zelenka, sail number "1" with
230
+ * Simulates the "Oak cliff DH Distance Race" R1 (see https://my.sapsailing.com/gwt/RaceBoard.html?regattaName=Oak+cliff+DH+Distance+Race&raceName=R1&leaderboardName=Oak+cliff+DH+Distance+Race&leaderboardGroupId=a3902560-6bfa-43be-85e1-2b82a4963416&eventId=bf48a59d-f2af-47b6-a2f7-a5b78b22b9f2)
231
+ * with a single competitor, Gallagher / Zelenka, sail number "1" with
231 232
* the marks pinged statically to establish the course. The track of Gallagher / Zelenka is provided as a track of
232 233
* their GPS positions. This could be the raw track, or it may be a filtered variant of the track with outliers
233 234
* removed or adjusted.<p>
... ...
@@ -236,9 +237,9 @@ public class JumpyTrackSmootheningTest {
236 237
* its calculation. As a result, a test may determine the impact filtering / adjusting the track may have on the
237 238
* mark passing analysis.
238 239
*/
239
- private DynamicTrackedRace createRace(DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack) throws PatchFailedException, ParseException {
240
+ private DynamicTrackedRace createRace(DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack) throws PatchFailedException, ParseException, InterruptedException {
240 241
final Competitor gallagherZelenka = competitorTrack.getTrackedItem();
241
- final DynamicTrackedRace trackedRace = createTrackedRace("Oak cliff DH Distance Race", "R1", BoatClassMasterdata.MELGES_24, gallagherZelenka);
242
+ final DynamicTrackedRaceWithMarkPassingCalculator trackedRace = createTrackedRace("Oak cliff DH Distance Race", "R1", BoatClassMasterdata.MELGES_24, gallagherZelenka);
242 243
final Series defaultSeries = trackedRace.getTrackedRegatta().getRegatta().getSeries().iterator().next();
243 244
final Fleet defaultFleet = defaultSeries.getFleets().iterator().next();
244 245
final RaceColumnInSeries r1RaceColumn = defaultSeries.getRaceColumns().iterator().next();
... ...
@@ -287,9 +288,8 @@ public class JumpyTrackSmootheningTest {
287 288
addFixedMarkPassingToRaceLog("2020-10-14T17:29:36Z", gallagherZelenka, 2, raceLog);
288 289
addFixedMarkPassingToRaceLog("2020-10-14T17:36:42Z", gallagherZelenka, 3, raceLog);
289 290
addFixedMarkPassingToRaceLog("2020-10-14T18:21:38Z", gallagherZelenka, 4, raceLog);
290
- trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0.0));
291
+ trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0.0)); // suspends mark passing calculator
291 292
final DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrackInRace = trackedRace.getTrack(gallagherZelenka);
292
- // TODO switch race into suspended mode to avoid updates during mass fix insertion:
293 293
competitorTrack.lockForRead();
294 294
try {
295 295
for (final GPSFixMoving fix : competitorTrack.getRawFixes()) {
... ...
@@ -298,12 +298,12 @@ public class JumpyTrackSmootheningTest {
298 298
} finally {
299 299
competitorTrack.unlockAfterRead();
300 300
}
301
- trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, 1.0));
302
- // TODO resume race
301
+ trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, 1.0)); // resumes mark passing calculator
302
+ trackedRace.getMarkPassingCalculator().waitUntilStopped(/* timeout in millis */ Duration.ONE_MINUTE.times(15).asMillis());
303 303
return trackedRace;
304 304
}
305 305
306
- private DynamicTrackedRace createTrackedRace(String regattaName, String name, BoatClassMasterdata boatClassMasterData, Competitor gallagherZelenka) {
306
+ private DynamicTrackedRaceWithMarkPassingCalculator createTrackedRace(String regattaName, String name, BoatClassMasterdata boatClassMasterData, Competitor gallagherZelenka) {
307 307
final BoatClassImpl boatClass = new BoatClassImpl(boatClassMasterData);
308 308
final TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(new RegattaImpl(regattaName, boatClass,
309 309
/* canBoatsOfCompetitorsChangePerRace */ false, /* competitorRegistrationType */ CompetitorRegistrationType.CLOSED,
... ...
@@ -315,7 +315,7 @@ public class JumpyTrackSmootheningTest {
315 315
final Map<Competitor, Boat> competitorsAndTheirBoats = Util.<Competitor, Boat>mapBuilder().put(gallagherZelenka, boat).build();
316 316
final Course course = new CourseImpl("R1 Course", Collections.emptySet());
317 317
final RaceDefinition race = new RaceDefinitionImpl(name, course, boatClass, competitorsAndTheirBoats, UUID.randomUUID());
318
- return new DynamicTrackedRaceImpl(trackedRegatta, race, /* sidelines */ Collections.emptySet(), new EmptyWindStore(), /* delayToLiveInMillis */ 1000,
318
+ return new DynamicTrackedRaceWithMarkPassingCalculator(trackedRegatta, race, /* sidelines */ Collections.emptySet(), new EmptyWindStore(), /* delayToLiveInMillis */ 1000,
319 319
WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* time over which to average speed: */ boatClass.getApproximateManeuverDurationInMilliseconds(),
320 320
/* useInternalMarkPassingAlgorithm */ true, OneDesignRankingMetric::new, mock(RaceLogAndTrackedRaceResolver.class), /* trackingConnectorInfo */ null, /* markPassingRaceFingerprintRegistry */ null);
321 321
}
java/com.sap.sailing.www/release_notes_admin.html
... ...
@@ -37,6 +37,10 @@
37 37
<li><tt>GEONAMES_ORG_USERNAMES</tt> for <tt>geonames.org.usernames</tt></li>
38 38
</ul>
39 39
If the system property is set, it takes precedence over the environment variable.</li>
40
+ <li>The source code of the Sailing Analytics including its companion apps (Race Manager App,
41
+ Buoy Pinger App, Sail Insight) has been published under the Apache 2.0 license on
42
+ GitHub. See <a href="https://github.com/SAP/sailing-analytics">https://github.com/SAP/sailing-analytics</a>.
43
+ Get engaged!</li>
40 44
</ul>
41 45
<h2 class="articleSubheadline">September 2025</h2>
42 46
<ul class="bulletList">
java/com.sap.sailing.xrr.resultimport/META-INF/MANIFEST.MF
... ...
@@ -24,5 +24,6 @@ Require-Bundle: com.sap.sailing.domain,
24 24
com.sap.sse.common,
25 25
com.sun.xml.bind.jaxb-impl;bundle-version="2.3.0",
26 26
com.sap.sailing.domain.shared.android,
27
- com.sap.sse
27
+ com.sap.sse,
28
+ com.sap.sse.security.common
28 29
Bundle-Activator: com.sap.sailing.xrr.resultimport.impl.Activator
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/useragent/UserAgentCheckerImpl.java
... ...
@@ -9,15 +9,14 @@ public class UserAgentCheckerImpl implements UserAgentChecker {
9 9
/**
10 10
* Version numbers indicate minimum required browser (20 = at least this version)
11 11
*/
12
- @SuppressWarnings("serial")
13 12
private static final HashMap<AgentTypes, Integer> MINIMUM_SUPPORTED_AGENTS = new HashMap<AgentTypes, Integer>() {
13
+ private static final long serialVersionUID = -3972648919899828625L;
14 14
{
15 15
put(AgentTypes.MSIE, 9);
16 16
put(AgentTypes.SAFARI, 5);
17 17
put(AgentTypes.OPERA, 10);
18 18
put(AgentTypes.FIREFOX, 10);
19 19
put(AgentTypes.CHROME, 20);
20
-
21 20
}
22 21
};
23 22