1f8b3173cd083a7bade16c3f62a6fc889a842b40
.github/workflows/create-docker-image.yml
| ... | ... | @@ -17,6 +17,18 @@ jobs: |
| 17 | 17 | if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} |
| 18 | 18 | runs-on: ubuntu-latest |
| 19 | 19 | steps: |
| 20 | + - name: Convert repo name to lowercase GHCR package |
|
| 21 | + id: ghcr |
|
| 22 | + env: |
|
| 23 | + GITHUB_REPOSITORY: ${{ github.repository }} |
|
| 24 | + run: | |
|
| 25 | + # $GITHUB_REPOSITORY is in format owner/repo |
|
| 26 | + OWNER=${GITHUB_REPOSITORY%%/*} # extract owner |
|
| 27 | + REPO=${GITHUB_REPOSITORY##*/} # extract repo |
|
| 28 | + OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') # lowercase owner |
|
| 29 | + REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]') # lowercase repo |
|
| 30 | + PACKAGE="$OWNER_LOWER/$REPO_LOWER" |
|
| 31 | + echo "PACKAGE=${PACKAGE}" >> $GITHUB_OUTPUT |
|
| 20 | 32 | - name: Checkout |
| 21 | 33 | uses: actions/checkout@v4 |
| 22 | 34 | with: |
| ... | ... | @@ -50,7 +62,7 @@ jobs: |
| 50 | 62 | - name: Download release |
| 51 | 63 | shell: bash |
| 52 | 64 | run: | |
| 53 | - RELEASE_TAR_GZ_FILENAME=$( configuration/github-download-release-assets.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.RELEASE_PREFIX }} ) |
|
| 65 | + RELEASE_TAR_GZ_FILENAME=$( configuration/github-download-release-assets.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.RELEASE_PREFIX }} ${{ github.repository }} ) |
|
| 54 | 66 | RELEASE=$( echo ${RELEASE_TAR_GZ_FILENAME} | sed -e 's/\.tar\.gz$//' ) |
| 55 | 67 | if [ -n ${RELEASE_TAR_GZ_FILENAME} ]; then |
| 56 | 68 | mv ${RELEASE_TAR_GZ_FILENAME} docker/ |
| ... | ... | @@ -60,7 +72,7 @@ jobs: |
| 60 | 72 | uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 |
| 61 | 73 | with: |
| 62 | 74 | build-args: RELEASE=${{ env.RELEASE }} |
| 63 | - tags: ghcr.io/sap/sailing-analytics:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest' || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-17' || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-21' || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-24' || '' }} |
|
| 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) || '' }} |
|
| 64 | 76 | annotations: | |
| 65 | 77 | maintainer=axel.uhl@sap.com |
| 66 | 78 | index:org.opencontainers.image.title=Sailing Analytics |
.github/workflows/merge-main-into-docker-17.yml
| ... | ... | @@ -1,29 +0,0 @@ |
| 1 | -name: Merge main branch into docker-24 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-24: |
|
| 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-24 |
|
| 19 | - fetch-depth: 0 # fetch the whole thing to make sure the histories merge |
|
| 20 | - - name: Merge main into docker-24 |
|
| 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-24 |
|
| 28 | - message: Auto-merging main into docker-24 after successful release build |
|
| 29 | - github_token: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }} |
.github/workflows/merge-main-into-docker-24.yml
| ... | ... | @@ -0,0 +1,29 @@ |
| 1 | +name: Merge main branch into docker-24 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-24: |
|
| 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-24 |
|
| 19 | + fetch-depth: 0 # fetch the whole thing to make sure the histories merge |
|
| 20 | + - name: Merge main into docker-24 |
|
| 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-24 |
|
| 28 | + message: Auto-merging main into docker-24 after successful release build |
|
| 29 | + github_token: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }} |
.github/workflows/release.yml
| ... | ... | @@ -5,6 +5,7 @@ on: |
| 5 | 5 | - 'wiki/**' |
| 6 | 6 | - '.github/workflows/*' |
| 7 | 7 | - '**/*.md' |
| 8 | + - 'README.md' |
|
| 8 | 9 | workflow_dispatch: |
| 9 | 10 | inputs: |
| 10 | 11 | skip_tests: |
| ... | ... | @@ -14,8 +15,8 @@ on: |
| 14 | 15 | description: Build with a local target platform (set to "true" to use local target platform) |
| 15 | 16 | default: 'false' |
| 16 | 17 | runner_cpus: |
| 17 | - description: Default is 16, but can be 4, 8, and 64 as well |
|
| 18 | - default: '16' |
|
| 18 | + description: Empty means ubuntu-latest, usually 4 CPUs with little disk/memory, but if you have runners named ubuntu-latest-Xcpu, it can be 4, 8, 16, and 64 as well, using, e.g., ubuntu-latest-16cpu; default is ubuntu-latest |
|
| 19 | + default: '' |
|
| 19 | 20 | firefox_version: |
| 20 | 21 | description: Version to use for Selenium tests; default is 137.0, but could, e.g., be "141.0" or "latest" |
| 21 | 22 | default: '140.0' |
| ... | ... | @@ -25,16 +26,36 @@ jobs: |
| 25 | 26 | permissions: |
| 26 | 27 | contents: write |
| 27 | 28 | checks: write |
| 28 | - runs-on: ubuntu-latest-${{ github.event.inputs.runner_cpus == '' && '16' || github.event.inputs.runner_cpus }}cpu |
|
| 29 | + actions: read |
|
| 30 | + runs-on: ${{ github.event.inputs.runner_cpus != '' && format('ubuntu-latest-{0}cpu', github.event.inputs.runner_cpus) || 'ubuntu-latest' }} |
|
| 29 | 31 | steps: |
| 32 | + - name: Free Disk Space (Ubuntu) |
|
| 33 | + uses: jlumbroso/free-disk-space@v1.3.1 |
|
| 34 | + with: |
|
| 35 | + # this might remove tools that are actually needed, |
|
| 36 | + # if set to "true" but frees about 6 GB |
|
| 37 | + tool-cache: false |
|
| 38 | + # all of these default to true, but feel free to set to |
|
| 39 | + # "false" if necessary for your workflow |
|
| 40 | + android: false |
|
| 41 | + dotnet: true |
|
| 42 | + haskell: true |
|
| 43 | + large-packages: true |
|
| 44 | + docker-images: false |
|
| 45 | + swap-storage: true |
|
| 30 | 46 | - name: Allocate swap space |
| 31 | 47 | shell: bash |
| 32 | 48 | run: | |
| 33 | - sudo dd if=/dev/zero of=/mnt/large-swapfile bs=1G count=10 |
|
| 49 | + sudo dd if=/dev/zero of=/mnt/large-swapfile bs=1G count=30 |
|
| 34 | 50 | sudo chmod 600 /mnt/large-swapfile |
| 35 | 51 | sudo mkswap /mnt/large-swapfile |
| 36 | 52 | sudo chown root:root /mnt/large-swapfile |
| 37 | 53 | sudo swapon /mnt/large-swapfile |
| 54 | + - name: Create TMP space under /mnt/tmp |
|
| 55 | + shell: bash |
|
| 56 | + run: | |
|
| 57 | + sudo mkdir /mnt/tmp |
|
| 58 | + sudo chmod 777 /mnt/tmp |
|
| 38 | 59 | - name: Collect Workflow Telemetry |
| 39 | 60 | uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0 |
| 40 | 61 | with: |
| ... | ... | @@ -79,10 +100,18 @@ jobs: |
| 79 | 100 | ports: '5672:5672' |
| 80 | 101 | - shell: bash |
| 81 | 102 | env: # Or as an environment variable |
| 82 | - APP_PARAMETERS: "-Daws.region=eu-west-1 -Dgoogle.maps.authenticationparams=${{ secrets.GOOGLE_MAPS_AUTHENTICATIONPARAMS }} -Daws.s3.test.s3AccessId=${{ secrets.AWS_S3_TEST_S3ACCESSID }} -Daws.s3.test.s3AccessKey=${{ secrets.AWS_S3_TEST_S3ACCESSKEY }} -Dgeonames.org.usernames=${{ secrets.GEONAMES_ORG_USERNAMES }}" |
|
| 103 | + TMP: /mnt/tmp |
|
| 104 | + AWS_S3_TEST_S3ACCESSID: ${{ secrets.AWS_S3_TEST_S3ACCESSID }} |
|
| 105 | + AWS_S3_TEST_S3ACCESSKEY: ${{ secrets.AWS_S3_TEST_S3ACCESSKEY }} |
|
| 106 | + GEONAMES_ORG_USERNAMES: ${{ secrets.GEONAMES_ORG_USERNAMES }} |
|
| 107 | + GOOGLE_MAPS_AUTHENTICATION_PARAMS: ${{ secrets.GOOGLE_MAPS_AUTHENTICATION_PARAMS }} |
|
| 108 | + APP_PARAMETERS: "-Daws.region=eu-west-1" |
|
| 83 | 109 | JAVA8_HOME: ${{env.JAVA_HOME_8_X64}} |
| 84 | 110 | run: | |
| 85 | - ./configuration/buildAndUpdateProduct.sh -x ${{ github.event.inputs.runner_cpus == '' && '16' || github.event.inputs.runner_cpus }} ${{ github.event.inputs.skip_tests == 'true' && '-t' || '' }} ${{ github.event.inputs.local_target_platform == 'true' && '-v' || '' }} build 2>&1 |
|
| 111 | + ./configuration/buildAndUpdateProduct.sh -x ${{ github.event.inputs.runner_cpus == '' && '4' || github.event.inputs.runner_cpus }} ${{ github.event.inputs.skip_tests == 'true' && '-t' || '' }} ${{ github.event.inputs.local_target_platform == 'true' && '-v' || '' }} build 2>&1 |
|
| 112 | + - name: show disk stats |
|
| 113 | + if: always() |
|
| 114 | + run: df -h |
|
| 86 | 115 | - name: Upload build log |
| 87 | 116 | uses: actions/upload-artifact@v4 |
| 88 | 117 | if: always() |
| ... | ... | @@ -156,7 +185,11 @@ jobs: |
| 156 | 185 | asset_name: release-notes.txt |
| 157 | 186 | asset_content_type: text/plain |
| 158 | 187 | - name: Trigger Hudson job |
| 159 | - if: always() |
|
| 188 | + env: |
|
| 189 | + HUDSON_JOB_USERNAME: ${{ secrets.HUDSON_JOB_USERNAME }} |
|
| 190 | + HUDSON_JOB_PASSWORD: ${{ secrets.HUDSON_JOB_PASSWORD }} |
|
| 191 | + HUDSON_JOB_TOKEN: ${{ secrets.HUDSON_JOB_TOKEN }} |
|
| 192 | + if: ${{ always() && env.HUDSON_JOB_USERNAME != '' && env.HUDSON_JOB_PASSWORD != '' && env.HUDSON_JOB_TOKEN != '' }} |
|
| 160 | 193 | shell: bash |
| 161 | 194 | run: | |
| 162 | 195 | if [ "${{ github.ref }}" = "refs/heads/main" ]; then |
Home.md
| ... | ... | @@ -158,10 +158,8 @@ SAP is at the center of today’s technology revolution, developing innovations |
| 158 | 158 | * [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]] |
| 159 | 159 | |
| 160 | 160 | ## Projects |
| 161 | -* [[Analytics on a stick|wiki/projects/analytics-on-a-stick]] |
|
| 162 | -* [[Consolidating User Stores (bug 4006 / 4018)|wiki/projects/consolidating-user-stores]] |
|
| 163 | -* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]] |
|
| 164 | 161 | * [[Management Console for Easier Administration|wiki/howto/development/management-console]] |
| 162 | +* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]] |
|
| 165 | 163 | |
| 166 | 164 | ## Events and Planning |
| 167 | 165 | * [[Project Planning (bigger development)|wiki/events/planning]] |
README.md
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | ## About this Project |
| 4 | 4 | |
| 5 | -The Sailing Analytics, formerly known as the "SAP Sailing Analytics," are a solution for portraying and analyzing sailing regattas, supporting training scenarios, and powering the vast archive at https://sapsailing.com. The solution consists of a cloud application with a web-based user interface, as well as three companion apps that integrate with the cloud application. This repository has the code for the cloud-based web application, and two of the three mobile apps (Buoy Pinger and Race Manager). The third companion app (Sail Insight) is found in [another repository](https://github.com/SailTracks/sailinsight). |
|
| 5 | +The Sailing Analytics, formerly known as the "SAP Sailing Analytics," are a solution for portraying and analyzing sailing regattas, supporting training scenarios, and powering the vast archive at https://sapsailing.com. The solution consists of a cloud application with a web-based user interface, as well as three companion apps that integrate with the cloud application. This repository has the code for the cloud-based web application, and two of the three mobile apps (Buoy Pinger and Race Manager). The third companion app (Sail Insight) is found in [another repository](https://github.com/SAP/sailing-analytics-sail-insight). |
|
| 6 | 6 | |
| 7 | 7 | ## Description |
| 8 | 8 | |
| ... | ... | @@ -32,11 +32,11 @@ Based on the ``docker/docker-compose.yml`` definition you should end up with thr |
| 32 | 32 | |
| 33 | 33 | Try a request to [``http://127.0.0.1:8888/index.html``](http://127.0.0.1:8888/index.html) or [``http://127.0.0.1:8888/gwt/status``](http://127.0.0.1:8888/gwt/status) to see if things worked. |
| 34 | 34 | |
| 35 | -To use Java 17, use the ``docker-compose-17.yml`` file instead: |
|
| 35 | +To use Java 24, use the ``docker-compose-24.yml`` file instead: |
|
| 36 | 36 | |
| 37 | 37 | ``` |
| 38 | 38 | cd docker |
| 39 | - docker-compose -f docker-compose-17.yml up |
|
| 39 | + docker-compose -f docker-compose-24.yml up |
|
| 40 | 40 | ``` |
| 41 | 41 | |
| 42 | 42 | ## Requirements |
| ... | ... | @@ -74,12 +74,123 @@ See [here](https://www.sapsailing.com/gwt/Home.html#/imprint/:) for a list of co |
| 74 | 74 | |
| 75 | 75 | ## Building and Running |
| 76 | 76 | |
| 77 | -This assumes you have completed the onboarding (see again [here](https://wiki.sapsailing.com/wiki/howto/onboarding)) successfully. To build, then invoke |
|
| 77 | +Builds usually run on [GitHub Actions](https://github.com/SAP/sailing-analytics/actions/workflows/release.yml) upon every push. A few repository secrets ensure that the build process has the permissions it needs. Pushes to the ``main``, ``docker-24`` and ``releases/*`` branches also publish a [release](https://github.com/SAP/sailing-analytics/releases) after a successful build. |
|
| 78 | + |
|
| 79 | +There are two options for building, detailed below; for both, you need to fulfill a few prerequisites. |
|
| 80 | + |
|
| 81 | +### Prerequisites |
|
| 82 | + |
|
| 83 | +If you have forked the repository and would like to run your own build, you will need the following prerequisites: |
|
| 84 | + |
|
| 85 | +#### Google Maps API Key |
|
| 86 | + |
|
| 87 | +Create or log on to your Google account and go to the [Google Cloud Console](https://console.cloud.google.com/apis/) and create an API key at least for the Maps JavaScript API. To later run the full product, while you're here, you can also create an API key for the YouTube Data API v3. |
|
| 88 | + |
|
| 89 | +#### AWS S3 Bucket for Upload Tests |
|
| 90 | + |
|
| 91 | +The build runs integration tests using the AWS API, requiring an access key with permission to upload to a bucket called ``sapsailing-automatic-upload-test``. Create that bucket in your AWS S3 environment. You can create an IAM user with CLI access and a permission policy that restricts write permissions to just that test bucket. You have to create the bucked in AWS region ``eu-west-1``. A permission policy for that user can look like this: |
|
| 92 | + |
|
| 93 | +``` |
|
| 94 | +{ |
|
| 95 | + "Version": "2012-10-17", |
|
| 96 | + "Statement": [ |
|
| 97 | + { |
|
| 98 | + "Sid": "Stmt1422015883000", |
|
| 99 | + "Effect": "Allow", |
|
| 100 | + "Action": [ |
|
| 101 | + "s3:AbortMultipartUpload", |
|
| 102 | + "s3:DeleteObject", |
|
| 103 | + "s3:DeleteObjectVersion", |
|
| 104 | + "s3:GetBucketAcl", |
|
| 105 | + "s3:GetBucketCORS", |
|
| 106 | + "s3:GetBucketLocation", |
|
| 107 | + "s3:GetBucketLogging", |
|
| 108 | + "s3:GetBucketNotification", |
|
| 109 | + "s3:GetBucketPolicy", |
|
| 110 | + "s3:GetBucketTagging", |
|
| 111 | + "s3:GetBucketVersioning", |
|
| 112 | + "s3:GetBucketWebsite", |
|
| 113 | + "s3:GetLifecycleConfiguration", |
|
| 114 | + "s3:GetObject", |
|
| 115 | + "s3:GetObjectAcl", |
|
| 116 | + "s3:GetObjectTorrent", |
|
| 117 | + "s3:GetObjectVersion", |
|
| 118 | + "s3:GetObjectVersionAcl", |
|
| 119 | + "s3:GetObjectVersionTorrent", |
|
| 120 | + "s3:ListAllMyBuckets", |
|
| 121 | + "s3:ListBucket", |
|
| 122 | + "s3:ListBucketMultipartUploads", |
|
| 123 | + "s3:ListBucketVersions", |
|
| 124 | + "s3:ListMultipartUploadParts", |
|
| 125 | + "s3:PutBucketLogging", |
|
| 126 | + "s3:PutBucketNotification", |
|
| 127 | + "s3:PutBucketPolicy", |
|
| 128 | + "s3:PutBucketTagging", |
|
| 129 | + "s3:PutBucketVersioning", |
|
| 130 | + "s3:PutBucketWebsite", |
|
| 131 | + "s3:PutLifecycleConfiguration", |
|
| 132 | + "s3:PutObject", |
|
| 133 | + "s3:PutObjectAcl", |
|
| 134 | + "s3:PutObjectVersionAcl", |
|
| 135 | + "s3:RestoreObject" |
|
| 136 | + ], |
|
| 137 | + "Resource": [ |
|
| 138 | + "arn:aws:s3:::sapsailing-automatic-upload-test/*", |
|
| 139 | + "arn:aws:s3:::sapsailing-automatic-upload-test" |
|
| 140 | + ] |
|
| 141 | + } |
|
| 142 | + ] |
|
| 143 | +} |
|
| 144 | +``` |
|
| 145 | + |
|
| 146 | +Create an access key for that user and note key ID and secret. |
|
| 147 | + |
|
| 148 | +#### Account(s) for geonames.org |
|
| 149 | + |
|
| 150 | +The build runs integration tests against [geonames.org](https://geonames.org). Use their login/sign-up form to create your user account and note its username. |
|
| 151 | + |
|
| 152 | +### Get the GitHub Actions build to work in your forked repository |
|
| 153 | + |
|
| 154 | +Assign the IDs and secrets from the prerequisites to repository secrets in your forked repository as follows: |
|
| 155 | + |
|
| 156 | +``` |
|
| 157 | +AWS_S3_TEST_S3ACCESSID: {your-S3-test-bucket-upload-token-ID} |
|
| 158 | +AWS_S3_TEST_S3ACCESSKEY: {key-for-your-S3-token} |
|
| 159 | +GEONAMES_ORG_USERNAMES: {comma-separated-list-of-geonames.org-usernames} |
|
| 160 | +GOOGLE_MAPS_AUTHENTICATION_PARAMS: key={your-Google-Maps-API-key} |
|
| 161 | +``` |
|
| 162 | + |
|
| 163 | +Then, manually trigger the ``release`` workflow with default options. You find the "Run workflow" drop-down in your forked repository under ``https://github.com/{your-github-user}/[your-repository-name}/actions/workflows/release.yml``. |
|
| 164 | + |
|
| 165 | +Release builds will trigger the ``create-docker-image`` workflow which will produce a Docker image of your release and publish it as a "ghcr" package in your repository. Note that package names are computed from the repository name by converting the latter to all lowercase characters. If you want to use your packages in the docker-compose configurations from the ``docker/`` folder, make sure to adjust the package name so it points to your own fork's package registry. |
|
| 166 | + |
|
| 167 | +If you want automatic validation of your changes to the ``main`` branch also for newer Java versions, you can use the ``merge-main-into-docker-24`` workflow. It requires another secret: |
|
| 168 | + |
|
| 169 | +``` |
|
| 170 | +REPO_TOKEN_FOR_MERGE_AND_PUSH: {a-GitHub-token-enabled-for-push} |
|
| 171 | +``` |
|
| 172 | + |
|
| 173 | +The workflow will launch automatically after a release has been performed for the ``main`` branch and will try to merge the latest ``main`` branch into ``docker-24`` and pushing the merge result if the merge was successful. This will then trigger a build for the ``docker-24`` branch which, if successul, will in turn produce a release and a docker image for use with Java 24. |
|
| 174 | + |
|
| 175 | +### Run a build locally |
|
| 176 | + |
|
| 177 | +This assumes you have completed the onboarding (see again [here](https://wiki.sapsailing.com/wiki/howto/onboarding)) successfully, including the Maven-specific parts such as having Maven installed (>= 3.9.x) and having a valid ``toolchains.xml`` file in your ~/.m2 folder. Furthermore, set and export the following environment variables: |
|
| 178 | + |
|
| 179 | +``` |
|
| 180 | +export AWS_S3_TEST_S3ACCESSID={your-S3-test-bucket-upload-token-ID} |
|
| 181 | +export AWS_S3_TEST_S3ACCESSKEY={key-for-your-S3-token} |
|
| 182 | +export GEONAMES_ORG_USERNAMES={comma-separated-list-of-geonames.org-usernames} |
|
| 183 | +export GOOGLE_MAPS_AUTHENTICATION_PARAMS=key={your-Google-Maps-API-key} |
|
| 184 | +export JAVA8_HOME={location-of-your-JDK8} |
|
| 185 | +export JAVA_HOME={location-of-your-JDK17-or-newer} |
|
| 186 | +``` |
|
| 187 | + |
|
| 188 | +To build, then invoke |
|
| 78 | 189 | |
| 79 | 190 | ``` |
| 80 | 191 | configuration/buildAndUpdateProduct.sh build |
| 81 | 192 | ``` |
| 82 | -This will build the Android companion apps first, then the web application. If the build was successful you can install the product locally by invoking |
|
| 193 | +This will build the Android companion apps first, then the web application. If you lack a proper Android SDK set-up, consider using the ``-a`` option to skip building the Android apps. If the build was successful you can install the product locally by invoking |
|
| 83 | 194 | |
| 84 | 195 | ``` |
| 85 | 196 | configuration/buildAndUpdateProduct.sh install [ -s <server-name> ] |
| ... | ... | @@ -99,9 +210,9 @@ Run the ``buildAndUpdateProduct.sh`` without any arguments to see the sub-comman |
| 99 | 210 | |
| 100 | 211 | ## Downloading, Installing and Running an Official Release |
| 101 | 212 | |
| 102 | -You need to have Java 8 installed. Get one from [here](https://tools.eu1.hana.ondemand.com/#cloud). Either ensure that this JVM's ``java`` executable in on the ``PATH`` or set ``JAVA_HOME`` appropriately. |
|
| 213 | +You need to have Java 8 installed. Get one from, e.g., [here](https://tools.eu1.hana.ondemand.com/#cloud). Either ensure that this JVM's ``java`` executable in on the ``PATH`` or set ``JAVA_HOME`` appropriately. |
|
| 103 | 214 | |
| 104 | -At [https://releases.sapsailing.com](https://releases.sapsailing.com) you find official product builds. To fetch and install one of them, make an empty directory, change into it and run the ``refreshInstance.sh`` command, e.g., like this: |
|
| 215 | +At [https://github.com/SAP/sailing-analytics/releases](https://github.com/SAP/sailing-analytics/releases) you find official product builds. To fetch and install one of them, make an empty directory, change into it and run the ``refreshInstance.sh`` command, e.g., like this: |
|
| 105 | 216 | ``` |
| 106 | 217 | mkdir sailinganalytics |
| 107 | 218 | cd sailinganalytics |
| ... | ... | @@ -113,7 +224,6 @@ This will download and install the latest release and configure it such that it |
| 113 | 224 | In addition to the necessary ``MONGODB_URI`` variable you may need to inject a few secrets into your runtime environment: |
| 114 | 225 | |
| 115 | 226 | - ``MANAGE2SAIL_ACCESS_TOKEN`` access token for result and regatta structure import from the Manage2Sail regatta management system |
| 116 | -- ``IGTIMI_CLIENT_ID`` / ``IGTIMI_CLIENT_SECRET`` credentials for ``igtimi.com`` in case you have one or more WindBot devices that you would like to integrate with |
|
| 117 | 227 | - ``GOOGLE_MAPS_AUTHENTICATION_PARAMS`` as in ``"key=..."`` or ``"client=..."``, required to display the Google Map in the race viewer. Obtain a Google Maps key from the Google Cloud Developer console, e.g., [here](https://console.cloud.google.com/apis/dashboard). |
| 118 | 228 | - ``YOUTUBE_API_KEY`` as in ``"key=..."``, required to analyze time stamps and durations of YouTube videos when linking to races. Obtain a YouTube API key from the Google Cloud Developer console, e.g., [here](https://console.cloud.google.com/apis/dashboard). |
| 119 | 229 |
REUSE.toml
| ... | ... | @@ -2,6 +2,7 @@ version = 1 |
| 2 | 2 | SPDX-PackageName = "sailing-analytics" |
| 3 | 3 | SPDX-PackageSupplier = "SAP Open Source Program Office ospo@sap.com" |
| 4 | 4 | SPDX-PackageDownloadLocation = "https://github.com/SAP/sailing-analytics" |
| 5 | +SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls." |
|
| 5 | 6 | |
| 6 | 7 | [[annotations]] |
| 7 | 8 | path = "java/com.google.gwt.servlet/**" |
configuration/buildAndUpdateProduct.sh
| ... | ... | @@ -222,7 +222,7 @@ echo TMP will be used for java.io.tmpdir and is $TMP |
| 222 | 222 | if [ "$TMP" = "" ]; then |
| 223 | 223 | export TMP=/tmp |
| 224 | 224 | fi |
| 225 | -extra="${extra} -Dgwt.workers=${GWT_WORKERS} -Djava.io.tmpdir=$TMP" |
|
| 225 | +extra="${extra} -Dgwt.workers=${GWT_WORKERS} -Djava.io.tmpdir=$TMP -Dgwt.workDir=$TMP" |
|
| 226 | 226 | extra="${extra} -Djdk.xml.maxGeneralEntitySizeLimit=0 -Djdk.xml.totalEntitySizeLimit=0" |
| 227 | 227 | |
| 228 | 228 | shift $((OPTIND-1)) |
configuration/environments_scripts/repo/usr/local/bin/update_authorized_keys_for_landscape_managers_if_changed
| ... | ... | @@ -28,7 +28,8 @@ if [ "${curl_exit_code}" = "0" ]; then |
| 28 | 28 | logger -t sailing "New SSH key changes for landscape managers (${last_change_millis} newer than ${PREVIOUS_CHANGE})" |
| 29 | 29 | if update_authorized_keys_for_landscape_managers "${BEARER_TOKEN}" "${BASE_URL}" "${LOGON_USER_HOME}" ; then |
| 30 | 30 | logger -t sailing "Updating SSH keys for landscape managers successful; updating ${LAST_CHANGE_FILE}" |
| 31 | - echo ${last_change_millis} >${LAST_CHANGE_FILE} |
|
| 31 | + # /var/run is writable only for root, so we need to be able to sudo: |
|
| 32 | + sudo bash -c "echo ${last_change_millis} >${LAST_CHANGE_FILE}" |
|
| 32 | 33 | else |
| 33 | 34 | logger -t sailing "Updating SSH keys for landscape managers failed with exit code $?; not updating ${LAST_CHANGE_FILE}" |
| 34 | 35 | fi |
configuration/environments_scripts/reverse_proxy/setup-disposable-reverse-proxy.sh
| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | #!/bin/bash |
| 2 | 2 | |
| 3 | -# Setup script for Amazon Linux 2023. May need to update macro definitions for the archive IP. |
|
| 3 | +# Setup script for Amazon Linux 2023. Use 50g root partition. |
|
| 4 | +# May need to update macro definitions for the archive IP. |
|
| 4 | 5 | # Parameter 1 is the IP and parameter 2 is the bearer token to be installed in the root home dir. |
| 5 | 6 | # Ensure that the security for requesting the metadata uses IMDSv1 |
| 6 | 7 | if [[ "$#" -ne 2 ]]; then |
configuration/github-copy-release-to-sapsailing-com.sh
| ... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 | # produce false matches for the "main-" prefix. |
| 18 | 18 | BEARER_TOKEN="${1}" |
| 19 | 19 | RELEASE_NAME_PREFIX="${2}" |
| 20 | -RELEASE_TAR_GZ_FILE_NAME=$( `dirname "${0}"`/github-download-release-assets.sh "${BEARER_TOKEN}" "${RELEASE_NAME_PREFIX}" ) |
|
| 20 | +RELEASE_TAR_GZ_FILE_NAME=$( `dirname "${0}"`/github-download-release-assets.sh "${BEARER_TOKEN}" "${RELEASE_NAME_PREFIX}" SAP/sailing-analytics ) |
|
| 21 | 21 | if [ "${RELEASE_TAR_GZ_FILE_NAME}" != "" ]; then |
| 22 | 22 | RELEASE_NAME=$( echo ${RELEASE_TAR_GZ_FILE_NAME} | sed -e 's/^\(.*\)-\([0-9]*\).tar.gz$/\1/' ) |
| 23 | 23 | RELEASE_TIMESTAMP=$( echo ${RELEASE_TAR_GZ_FILE_NAME} | sed -e 's/^\(.*\)-\([0-9]*\).tar.gz$/\2/' ) |
configuration/github-download-release-assets.sh
| ... | ... | @@ -5,9 +5,9 @@ |
| 5 | 5 | # always be 0. |
| 6 | 6 | # |
| 7 | 7 | # Usage: |
| 8 | -# ./github-download-release-assets.sh {BEARER_TOKEN} {release-name-prefix} |
|
| 8 | +# ./github-download-release-assets.sh {BEARER_TOKEN} {release-name-prefix} {repository-name} |
|
| 9 | 9 | # For example: |
| 10 | -# ./github-download-release-assets.sh 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 main- |
|
| 10 | +# ./github-download-release-assets.sh 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 main- SAP/sailing-analytics |
|
| 11 | 11 | # which will download the latest release tar.gz and release-notes.txt of the main branch (main-xxxxxxxxxxx). |
| 12 | 12 | # Note the "-" at the end of the "main-" prefix specifier; this way we're making name |
| 13 | 13 | # clashes with releases whose name happens to start with "main" unlikely. This |
| ... | ... | @@ -15,7 +15,8 @@ |
| 15 | 15 | # produce false matches for the "main-" prefix. |
| 16 | 16 | BEARER_TOKEN="${1}" |
| 17 | 17 | RELEASE_NAME_PREFIX="${2}" |
| 18 | -RELEASES=$( curl -L -H 'Authorization: Bearer '${BEARER_TOKEN} https://api.github.com/repos/SAP/sailing-analytics/releases 2>/dev/null ) |
|
| 18 | +GITHUB_REPOSITORY="${3}" |
|
| 19 | +RELEASES=$( curl -L -H 'Authorization: Bearer '${BEARER_TOKEN} https://api.github.com/repos/${GITHUB_REPOSITORY}/releases 2>/dev/null ) |
|
| 19 | 20 | RELEASE_NOTES_TXT_ASSET_ID=$( echo "${RELEASES}" | jq -r 'sort_by(.published_at) | reverse | map(select(.name | startswith("'${RELEASE_NAME_PREFIX}'")))[0].assets[] | select(.content_type=="text/plain").id' 2>/dev/null) |
| 20 | 21 | if [ "$?" -ne "0" ]; then |
| 21 | 22 | echo "No release with prefix ${RELEASE_NAME_PREFIX} found. Not trying to download/upload anything." >&2 |
| ... | ... | @@ -26,7 +27,7 @@ else |
| 26 | 27 | RELEASE_TIMESTAMP=$( echo ${RELEASE_FULL_NAME} | sed -e 's/^\(.*\)-\([0-9]*\)$/\2/' ) |
| 27 | 28 | echo "Found release ${RELEASE_FULL_NAME} with name ${RELEASE_NAME} and time stamp ${RELEASE_TIMESTAMP}, notes ID is ${RELEASE_NOTES_TXT_ASSET_ID}, tarball ID is ${RELEASE_TAR_GZ_ASSET_ID}" >&2 |
| 28 | 29 | RELEASE_TAR_GZ_FILE_NAME="${RELEASE_FULL_NAME}.tar.gz" |
| 29 | - curl -o "${RELEASE_TAR_GZ_FILE_NAME}" -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/SAP/sailing-analytics/releases/assets/'${RELEASE_TAR_GZ_ASSET_ID} |
|
| 30 | - curl -o release-notes.txt -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/SAP/sailing-analytics/releases/assets/'${RELEASE_NOTES_TXT_ASSET_ID} |
|
| 30 | + curl -o "${RELEASE_TAR_GZ_FILE_NAME}" -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/'${GITHUB_REPOSITORY}'/releases/assets/'${RELEASE_TAR_GZ_ASSET_ID} |
|
| 31 | + curl -o release-notes.txt -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/'${GITHUB_REPOSITORY}'/releases/assets/'${RELEASE_NOTES_TXT_ASSET_ID} |
|
| 31 | 32 | echo "${RELEASE_TAR_GZ_FILE_NAME}" |
| 32 | 33 | fi |
configuration/github-download-workflow-artifacts.sh
| ... | ... | @@ -12,12 +12,13 @@ |
| 12 | 12 | # an exit status of 2 is returned. |
| 13 | 13 | BRANCH="${1}" |
| 14 | 14 | BEARER_TOKEN="${2}" |
| 15 | +GITHUB_REPOSITORY="${3}" |
|
| 15 | 16 | UNIX_TIME=$( date +%s ) |
| 16 | 17 | UNIX_DATE=$( date --iso-8601=second ) |
| 17 | 18 | UNIX_TIME_YESTERDAY=$(( UNIX_TIME - 10*24*3600 )) # look back ten days in time, trying to catch even re-runs of older jobs |
| 18 | 19 | DATE_YESTERDAY=$( date --iso-8601=second -d @${UNIX_TIME_YESTERDAY} ) |
| 19 | 20 | HEADERS_FILE=$( mktemp headersXXXXX ) |
| 20 | -NEXT_PAGE="https://api.github.com/repos/SAP/sailing-analytics/actions/runs?created=${DATE_YESTERDAY/+/%2B}..${UNIX_DATE/+/%2B}&per_page=100" |
|
| 21 | +NEXT_PAGE="https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs?created=${DATE_YESTERDAY/+/%2B}..${UNIX_DATE/+/%2B}&per_page=100" |
|
| 21 | 22 | ARTIFACTS_JSON="" |
| 22 | 23 | LATEST_RUN_STARTED_AT="0000-00-00T00:00:00Z" |
| 23 | 24 | # Now go through the pages as long as we have a non-empty NEXT_PAGE URL and find the completed "release" workflow that was started last |
docker/makeImageForLatestRelease
| ... | ... | @@ -1,5 +1,11 @@ |
| 1 | 1 | #!/bin/bash |
| 2 | -release_prefix=$1 |
|
| 2 | +release_prefix="$1" |
|
| 3 | +GITHUB_REPOSITORY="$2" |
|
| 4 | +OWNER=${GITHUB_REPOSITORY%%/*} |
|
| 5 | +OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') |
|
| 6 | +REPO=${GITHUB_REPOSITORY##*/} |
|
| 7 | +REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]') |
|
| 8 | +PACKAGE="$OWNER_LOWER/$REPO_LOWER" |
|
| 3 | 9 | GITROOT="`dirname $0`/.." |
| 4 | 10 | DOCKERDIR="${GITROOT}/docker" |
| 5 | 11 | DOCKERFILE="$DOCKERDIR/Dockerfile" |
| ... | ... | @@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then |
| 8 | 14 | release_prefix="main-" |
| 9 | 15 | fi |
| 10 | 16 | pushd "${DOCKERDIR}" |
| 11 | -RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" ) |
|
| 17 | +RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" ) |
|
| 12 | 18 | if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then |
| 13 | 19 | echo "No release with prefix ${release_prefix} found" >&2 |
| 14 | 20 | else |
| ... | ... | @@ -20,7 +26,7 @@ else |
| 20 | 26 | cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR" |
| 21 | 27 | cd "$DOCKERDIR" |
| 22 | 28 | docker buildx create --name=container --driver=docker-container --use --bootstrap |
| 23 | - docker buildx build --builder=container --platform=linux/amd64,linux/arm64 --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} --push . |
|
| 29 | + docker buildx build --builder=container --platform=linux/amd64,linux/arm64 --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} --push . |
|
| 24 | 30 | echo "Cleaning up..." |
| 25 | 31 | rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt |
| 26 | 32 | fi |
docker/makeImageForLatestRelease-arm
| ... | ... | @@ -1,5 +1,11 @@ |
| 1 | 1 | #!/bin/bash |
| 2 | -release_prefix=$1 |
|
| 2 | +release_prefix="$1" |
|
| 3 | +GITHUB_REPOSITORY="$2" |
|
| 4 | +OWNER=${GITHUB_REPOSITORY%%/*} |
|
| 5 | +OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') |
|
| 6 | +REPO=${GITHUB_REPOSITORY##*/} |
|
| 7 | +REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]') |
|
| 8 | +PACKAGE="$OWNER_LOWER/$REPO_LOWER" |
|
| 3 | 9 | GITROOT="`dirname $0`/.." |
| 4 | 10 | DOCKERDIR="${GITROOT}/docker" |
| 5 | 11 | DOCKERFILE="$DOCKERDIR/Dockerfile" |
| ... | ... | @@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then |
| 8 | 14 | release_prefix="main-" |
| 9 | 15 | fi |
| 10 | 16 | pushd "${DOCKERDIR}" |
| 11 | -RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" ) |
|
| 17 | +RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" ) |
|
| 12 | 18 | if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then |
| 13 | 19 | echo "No release with prefix ${release_prefix} found" >&2 |
| 14 | 20 | else |
| ... | ... | @@ -19,13 +25,13 @@ else |
| 19 | 25 | cp "$GITROOT/java/target/start" "$DOCKERDIR" |
| 20 | 26 | cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR" |
| 21 | 27 | cd "$DOCKERDIR" |
| 22 | - docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release}-arm . |
|
| 28 | + docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release}-arm . |
|
| 23 | 29 | echo "Cleaning up..." |
| 24 | 30 | rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt |
| 25 | - docker push ghcr.io/sap/sailing-analytics:${release}-arm |
|
| 31 | + docker push ghcr.io/${PACKAGE}:${release}-arm |
|
| 26 | 32 | if [ "$SET_LATEST" = "1" ]; then |
| 27 | - docker tag ghcr.io/sap/sailing-analytics:${release}-arm ghcr.io/sap/sailing-analytics:latest-arm |
|
| 28 | - docker push ghcr.io/sap/sailing-analytics:latest-arm |
|
| 33 | + docker tag ghcr.io/${PACKAGE}:${release}-arm ghcr.io/${PACKAGE}:latest-arm |
|
| 34 | + docker push ghcr.io/${PACKAGE}:latest-arm |
|
| 29 | 35 | fi |
| 30 | 36 | fi |
| 31 | 37 | popd |
docker/makeImageForLatestRelease-on-sapmachine11
| ... | ... | @@ -1,5 +1,11 @@ |
| 1 | 1 | #!/bin/bash |
| 2 | -release_prefix=$1 |
|
| 2 | +release_prefix="$1" |
|
| 3 | +GITHUB_REPOSITORY="$2" |
|
| 4 | +OWNER=${GITHUB_REPOSITORY%%/*} |
|
| 5 | +OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') |
|
| 6 | +REPO=${GITHUB_REPOSITORY##*/} |
|
| 7 | +REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]') |
|
| 8 | +PACKAGE="$OWNER_LOWER/$REPO_LOWER" |
|
| 3 | 9 | GITROOT="`dirname $0`/.." |
| 4 | 10 | DOCKERDIR="${GITROOT}/docker" |
| 5 | 11 | DOCKERFILE="$DOCKERDIR/Dockerfile" |
| ... | ... | @@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then |
| 8 | 14 | release_prefix="docker-11-" |
| 9 | 15 | fi |
| 10 | 16 | pushd "${DOCKERDIR}" |
| 11 | -RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" ) |
|
| 17 | +RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" ) |
|
| 12 | 18 | if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then |
| 13 | 19 | echo "No release with prefix ${release_prefix} found" >&2 |
| 14 | 20 | else |
| ... | ... | @@ -19,13 +25,13 @@ else |
| 19 | 25 | cp "$GITROOT/java/target/start" "$DOCKERDIR" |
| 20 | 26 | cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR" |
| 21 | 27 | cd "$DOCKERDIR" |
| 22 | - docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} -f Dockerfile_sapsailing_on_sapmachine11 . |
|
| 28 | + docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} -f Dockerfile_sapsailing_on_sapmachine11 . |
|
| 23 | 29 | echo "Cleaning up..." |
| 24 | 30 | rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt |
| 25 | - docker push ghcr.io/sap/sailing-analytics:${release} |
|
| 31 | + docker push ghcr.io/${PACKAGE}:${release} |
|
| 26 | 32 | if [ "$SET_LATEST" = "1" ]; then |
| 27 | - docker tag ghcr.io/sap/sailing-analytics:${release} ghcr.io/sap/sailing-analytics:latest-11 |
|
| 28 | - docker push ghcr.io/sap/sailing-analytics:latest-11 |
|
| 33 | + docker tag ghcr.io/${PACKAGE}:${release} ghcr.io/${PACKAGE}:latest-11 |
|
| 34 | + docker push ghcr.io/${PACKAGE}:latest-11 |
|
| 29 | 35 | fi |
| 30 | 36 | fi |
| 31 | 37 | popd |
docker/makeImageForLatestRelease-on-sapmachine17
| ... | ... | @@ -1,5 +1,11 @@ |
| 1 | 1 | #!/bin/bash |
| 2 | -release_prefix=$1 |
|
| 2 | +release_prefix="$1" |
|
| 3 | +GITHUB_REPOSITORY="$2" |
|
| 4 | +OWNER=${GITHUB_REPOSITORY%%/*} |
|
| 5 | +OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') |
|
| 6 | +REPO=${GITHUB_REPOSITORY##*/} |
|
| 7 | +REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]') |
|
| 8 | +PACKAGE="$OWNER_LOWER/$REPO_LOWER" |
|
| 3 | 9 | GITROOT="`dirname $0`/.." |
| 4 | 10 | DOCKERDIR="${GITROOT}/docker" |
| 5 | 11 | DOCKERFILE="$DOCKERDIR/Dockerfile" |
| ... | ... | @@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then |
| 8 | 14 | release_prefix="docker-17-" |
| 9 | 15 | fi |
| 10 | 16 | pushd "${DOCKERDIR}" |
| 11 | -RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" ) |
|
| 17 | +RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" ) |
|
| 12 | 18 | if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then |
| 13 | 19 | echo "No release with prefix ${release_prefix} found" >&2 |
| 14 | 20 | else |
| ... | ... | @@ -19,13 +25,13 @@ else |
| 19 | 25 | cp "$GITROOT/java/target/start" "$DOCKERDIR" |
| 20 | 26 | cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR" |
| 21 | 27 | cd "$DOCKERDIR" |
| 22 | - docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} -f Dockerfile_sapsailing_on_sapmachine17 . |
|
| 28 | + docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} -f Dockerfile_sapsailing_on_sapmachine17 . |
|
| 23 | 29 | echo "Cleaning up..." |
| 24 | 30 | rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt |
| 25 | - docker push ghcr.io/sap/sailing-analytics:${release} |
|
| 31 | + docker push ghcr.io/${PACKAGE}:${release} |
|
| 26 | 32 | if [ "$SET_LATEST" = "1" ]; then |
| 27 | - docker tag ghcr.io/sap/sailing-analytics:${release} ghcr.io/sap/sailing-analytics:latest-17 |
|
| 28 | - docker push ghcr.io/sap/sailing-analytics:latest-17 |
|
| 33 | + docker tag ghcr.io/${PACKAGE}:${release} ghcr.io/${PACKAGE}:latest-17 |
|
| 34 | + docker push ghcr.io/${PACKAGE}:latest-17 |
|
| 29 | 35 | fi |
| 30 | 36 | fi |
| 31 | 37 | popd |
java/com.sap.sailing.domain.igtimiadapter.persistence/src/com/sap/sailing/domain/igtimiadapter/persistence/impl/DomainObjectFactoryImpl.java
| ... | ... | @@ -96,8 +96,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { |
| 96 | 96 | final Document query = new Document(); |
| 97 | 97 | appendMultiTimeRangeQuery(query, timeRanges); |
| 98 | 98 | query.append(FieldNames.IGTIMI_MESSAGES_DEVICE_SERIAL_NUMBER.name(), deviceSerialNumber); |
| 99 | - final Iterable<Document> queryResult = clientSessionOrNull == null ? messagesCollection.find(query) |
|
| 100 | - : messagesCollection.find(clientSessionOrNull, query); |
|
| 99 | + final Iterable<Document> queryResult = (clientSessionOrNull == null ? messagesCollection.find(query) |
|
| 100 | + : messagesCollection.find(clientSessionOrNull, query)).sort(Sorts.ascending(FieldNames.IGTIMI_MESSAGES_TIMESTAMP.name())); |
|
| 101 | 101 | result = Util.filter(Util.map(queryResult, |
| 102 | 102 | doc->{ |
| 103 | 103 | try { |
java/com.sap.sailing.domain.igtimiadapter.server/src/com/sap/sailing/domain/igtimiadapter/server/riot/RiotServer.java
| ... | ... | @@ -53,9 +53,9 @@ import com.sap.sse.replication.Replicable; |
| 53 | 53 | * can be registered with this server using the {@link #addListener(BulkFixReceiver)} method. |
| 54 | 54 | * <p> |
| 55 | 55 | * |
| 56 | - * When combined with {@link IgtimiWindReceiver} (which is such a {@link BulkFixReceiver}, |
|
| 56 | + * When combined with {@link IgtimiWindReceiver} (which is such a {@link BulkFixReceiver}), |
|
| 57 | 57 | * {@link IgtimiWindListener}s can be registered on the wind receiver, just as they can |
| 58 | - * for a websocket connection. |
|
| 58 | + * for a websocket connection.<p> |
|
| 59 | 59 | * |
| 60 | 60 | * The server uses <tt>java.nio</tt> and {@link ServerSocketChannel}s, avoiding the creation |
| 61 | 61 | * of a thread per connection.<p> |
java/com.sap.sailing.domain.igtimiadapter.server/src/com/sap/sailing/domain/igtimiadapter/server/riot/impl/RiotServerImpl.java
| ... | ... | @@ -337,24 +337,26 @@ public class RiotServerImpl extends AbstractReplicableWithObjectInputStream<Repl |
| 337 | 337 | // obtain the securityService only if connections exist; otherwise, we may still be in start-up mode |
| 338 | 338 | final SecurityService securityService = liveWebSocketConnections.isEmpty() ? null : getSecurityService(); |
| 339 | 339 | for (final RiotWebsocketHandler webSocketClient : liveWebSocketConnections) { |
| 340 | - final User user = webSocketClient.getAuthenticatedUser(); |
|
| 341 | - final OwnershipAnnotation deviceOwnership = securityService.getOwnership(device.getIdentifier()); |
|
| 342 | - final AccessControlListAnnotation deviceAccessControlList = securityService.getAccessControlList(device.getIdentifier()); |
|
| 343 | - if (!Util.isEmpty(Util.filter(daws, daw->{ |
|
| 344 | - final OwnershipAnnotation dawOownership = securityService.getOwnership(daw.getIdentifier()); |
|
| 345 | - final AccessControlListAnnotation dawAccessControlList = securityService.getAccessControlList(daw.getIdentifier()); |
|
| 346 | - return PermissionChecker.isPermitted( |
|
| 347 | - daw.getIdentifier().getPermission(DefaultActions.READ), |
|
| 340 | + if (webSocketClient.getDeviceSerialNumbers().contains(deviceSerialNumber)) { |
|
| 341 | + final User user = webSocketClient.getAuthenticatedUser(); |
|
| 342 | + final OwnershipAnnotation deviceOwnership = securityService.getOwnership(device.getIdentifier()); |
|
| 343 | + final AccessControlListAnnotation deviceAccessControlList = securityService.getAccessControlList(device.getIdentifier()); |
|
| 344 | + if (!Util.isEmpty(Util.filter(daws, daw->{ |
|
| 345 | + final OwnershipAnnotation dawOownership = securityService.getOwnership(daw.getIdentifier()); |
|
| 346 | + final AccessControlListAnnotation dawAccessControlList = securityService.getAccessControlList(daw.getIdentifier()); |
|
| 347 | + return PermissionChecker.isPermitted( |
|
| 348 | + daw.getIdentifier().getPermission(DefaultActions.READ), |
|
| 349 | + user, securityService.getAllUser(), |
|
| 350 | + dawOownership==null?null:dawOownership.getAnnotation(), |
|
| 351 | + dawAccessControlList==null?null:dawAccessControlList.getAnnotation()); |
|
| 352 | + })) |
|
| 353 | + && PermissionChecker.isPermitted( |
|
| 354 | + device.getIdentifier().getPermission(DefaultActions.READ), |
|
| 348 | 355 | user, securityService.getAllUser(), |
| 349 | - dawOownership==null?null:dawOownership.getAnnotation(), |
|
| 350 | - dawAccessControlList==null?null:dawAccessControlList.getAnnotation()); |
|
| 351 | - })) |
|
| 352 | - && PermissionChecker.isPermitted( |
|
| 353 | - device.getIdentifier().getPermission(DefaultActions.READ), |
|
| 354 | - user, securityService.getAllUser(), |
|
| 355 | - deviceOwnership==null?null:deviceOwnership.getAnnotation(), |
|
| 356 | - deviceAccessControlList==null?null:deviceAccessControlList.getAnnotation()) ) { |
|
| 357 | - webSocketClient.sendBytesByFuture(ByteBuffer.wrap(messageAsBytes)); |
|
| 356 | + deviceOwnership==null?null:deviceOwnership.getAnnotation(), |
|
| 357 | + deviceAccessControlList==null?null:deviceAccessControlList.getAnnotation()) ) { |
|
| 358 | + webSocketClient.sendBytesByFuture(ByteBuffer.wrap(messageAsBytes)); |
|
| 359 | + } |
|
| 358 | 360 | } |
| 359 | 361 | } |
| 360 | 362 | } |
java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java
| ... | ... | @@ -188,8 +188,9 @@ public class IgtimiWindReceiver implements BulkFixReceiver { |
| 188 | 188 | } |
| 189 | 189 | |
| 190 | 190 | /** |
| 191 | - * Returns a wind fix produced out of an AWS/AWA and other fixes, as well as the fixes used for this, including a {@link BatteryLevel} |
|
| 192 | - * fix, if any was found, although this did technically not contribute to the production of the {@link Wind} object. |
|
| 191 | + * Returns a wind fix produced out of an AWS/AWA and other fixes, as well as the fixes used for this (in case of |
|
| 192 | + * interpolation between two fixes, the older one of the two is returned), including a {@link BatteryLevel} fix, if |
|
| 193 | + * any was found, although this did technically not contribute to the production of the {@link Wind} object. |
|
| 193 | 194 | */ |
| 194 | 195 | private Pair<Wind, Set<Fix>> getWind(final TimePoint timePoint, String deviceSerialNumber) throws ClassNotFoundException, IOException, ParseException { |
| 195 | 196 | final Wind result; |
| ... | ... | @@ -213,7 +214,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { |
| 213 | 214 | final SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection); |
| 214 | 215 | /* |
| 215 | 216 | * Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to |
| 216 | - * improve precision of boat speed / coarse over SOG/COG measurements: |
|
| 217 | + * improve precision of boat speed / course over SOG/COG measurements: |
|
| 217 | 218 | * |
| 218 | 219 | * "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of |
| 219 | 220 | * smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter |
| ... | ... | @@ -260,7 +261,11 @@ public class IgtimiWindReceiver implements BulkFixReceiver { |
| 260 | 261 | /** |
| 261 | 262 | * Searches for the last fix at or before {@code timePoint} in the given {@link trackToCleanUp}. If such a fix is |
| 262 | 263 | * found, adds the given fix to {@code fixesUsed} and clears all fixes from the {@code trackToCleanUp} that are |
| 263 | - * earlier than or at the {@code timePoint} given. |
|
| 264 | + * earlier than the {@code timePoint} given. This in particular preserves at least one live fix which will "hide" |
|
| 265 | + * older, e.g., buffered fixes that may be received when a WindBot device sends them out-of-order. For old, buffered |
|
| 266 | + * fixes this may still mean that could be dropped prior to being converted into a {@link Wind} object with an older |
|
| 267 | + * time stamp, but usually---and particularly with buffered data from previous days---this shouldn't be a problem. |
|
| 268 | + * Also, old data can still be imported at a later point in time. |
|
| 264 | 269 | * <p> |
| 265 | 270 | * |
| 266 | 271 | * This assumes that the {@code fix} passed has been "consumed" now, and earlier fixes will not be relevant anymore. |
| ... | ... | @@ -270,7 +275,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { |
| 270 | 275 | final FixType fix = trackToCleanUp.getLastFixAtOrBefore(timePoint); |
| 271 | 276 | if (fix != null) { |
| 272 | 277 | fixesUsed.add(fix); |
| 273 | - trackToCleanUp.removeAllUpToAndIncluding(fix); |
|
| 278 | + trackToCleanUp.removeAllUpToExcluding(fix); |
|
| 274 | 279 | } |
| 275 | 280 | } |
| 276 | 281 |
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TrackImpl.java
| ... | ... | @@ -292,6 +292,11 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> { |
| 292 | 292 | return result; |
| 293 | 293 | } |
| 294 | 294 | |
| 295 | + /** |
|
| 296 | + * Calculates a linear interpolation of values based on their time points and a target time point that is expected |
|
| 297 | + * to be in between (inclusive) the two time points for the two values. If the two time points for the two values |
|
| 298 | + * are equal, the average of the two values is returned. |
|
| 299 | + */ |
|
| 295 | 300 | private <V, T> T timeBasedAverage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) { |
| 296 | 301 | final T acc; |
| 297 | 302 | if (timePoint1.equals(timePoint2)) { |
java/com.sap.sailing.domain.test/SailingDomainTest (No Proxy, Tunnel).launch
| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
| 2 | 2 | <launchConfiguration type="org.eclipse.jdt.junit.launchconfig"> |
| 3 | + <booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/> |
|
| 3 | 4 | <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> |
| 4 | 5 | <listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/> |
| 5 | 6 | </listAttribute> |
| ... | ... | @@ -10,7 +11,7 @@ |
| 10 | 11 | <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/> |
| 11 | 12 | <listEntry value="org.eclipse.debug.ui.launchGroup.run"/> |
| 12 | 13 | </listAttribute> |
| 13 | - <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src=/test=/true=/<com.sap.sailing.domain.test"/> |
|
| 14 | + <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src<com.sap.sailing.domain.test"/> |
|
| 14 | 15 | <booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/> |
| 15 | 16 | <stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/> |
| 16 | 17 | <stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/> |
java/com.sap.sailing.domain.test/src/LeaderboardCourseChangeWithEliminationTest.java
| ... | ... | @@ -1,14 +0,0 @@ |
| 1 | -import com.sap.sailing.domain.base.Regatta; |
|
| 2 | -import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations; |
|
| 3 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 4 | -import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination; |
|
| 5 | -import com.sap.sailing.domain.test.LeaderboardCourseChangeTest; |
|
| 6 | - |
|
| 7 | -public class LeaderboardCourseChangeWithEliminationTest extends LeaderboardCourseChangeTest { |
|
| 8 | - @Override |
|
| 9 | - protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta, |
|
| 10 | - ThresholdBasedResultDiscardingRule discardingRule) { |
|
| 11 | - return new DelegatingRegattaLeaderboardWithCompetitorElimination( |
|
| 12 | - ()->super.createRegattaLeaderboard(regatta, discardingRule), "Test leaderboard with elimination"); |
|
| 13 | - } |
|
| 14 | -} |
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeWithEliminationTest.java
| ... | ... | @@ -0,0 +1,15 @@ |
| 1 | +package com.sap.sailing.domain.test; |
|
| 2 | + |
|
| 3 | +import com.sap.sailing.domain.base.Regatta; |
|
| 4 | +import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations; |
|
| 5 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 6 | +import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination; |
|
| 7 | + |
|
| 8 | +public class LeaderboardCourseChangeWithEliminationTest extends LeaderboardCourseChangeTest { |
|
| 9 | + @Override |
|
| 10 | + protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta, |
|
| 11 | + ThresholdBasedResultDiscardingRule discardingRule) { |
|
| 12 | + return new DelegatingRegattaLeaderboardWithCompetitorElimination( |
|
| 13 | + ()->super.createRegattaLeaderboard(regatta, discardingRule), "Test leaderboard with elimination"); |
|
| 14 | + } |
|
| 15 | +} |
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java
| ... | ... | @@ -2918,16 +2918,10 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki |
| 2918 | 2918 | assertTrue(Util.indexOf(rankedCompetitors, lastRaceGoldParticipant) < Util.indexOf(rankedCompetitors, silverParticipant)); |
| 2919 | 2919 | } |
| 2920 | 2920 | } |
| 2921 | - // assert that theUntrackedCompetitorInLastRace ended up between the last race's silver and gold fleet participants |
|
| 2922 | - // based on the "extreme fleet" rule: |
|
| 2921 | + // assert that theUntrackedCompetitorInLastRace ended up ranked worse than all participants of ranked fleets: |
|
| 2923 | 2922 | for (final Competitor c : competitors) { |
| 2924 | 2923 | if (c != theUntrackedCompetitorInLastRace) { |
| 2925 | - if (lastRaceGold.contains(c) || medal.contains(c)) { |
|
| 2926 | - assertTrue(Util.indexOf(rankedCompetitors, c) < Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace)); |
|
| 2927 | - } else { |
|
| 2928 | - assertTrue(silver.contains(c)); |
|
| 2929 | - assertTrue(Util.indexOf(rankedCompetitors, c) > Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace)); |
|
| 2930 | - } |
|
| 2924 | + assertTrue(Util.indexOf(rankedCompetitors, c) < Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace)); |
|
| 2931 | 2925 | } |
| 2932 | 2926 | } |
| 2933 | 2927 | } |
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OnlineTracTracBasedTest.java
| ... | ... | @@ -149,12 +149,9 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest i |
| 149 | 149 | case Begin: |
| 150 | 150 | logger.info("Stored data begin"); |
| 151 | 151 | lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0); |
| 152 | - new Thread(()->{ |
|
| 153 | - final RaceDefinition raceDefinition = domainFactory.getAndWaitForRaceDefinition(getTracTracRace().getId(), /* timeout in millis */ 10000); |
|
| 154 | - if (trackedRegatta != null && raceDefinition != null && trackedRegatta.getTrackedRace(raceDefinition) != null) { |
|
| 155 | - trackedRegatta.getTrackedRace(raceDefinition).onStatusChanged(OnlineTracTracBasedTest.this, lastStatus); |
|
| 156 | - } |
|
| 157 | - }, "waiting for race definition for race "+getTracTracRace().getId()).start(); |
|
| 152 | + if (getTrackedRace() != null) { |
|
| 153 | + getTrackedRace().onStatusChanged(OnlineTracTracBasedTest.this, lastStatus); |
|
| 154 | + } |
|
| 158 | 155 | break; |
| 159 | 156 | case End: |
| 160 | 157 | logger.info("Stored data end. Delaying status update on tracked race "+getTrackedRace()+" until all events queued in receivers so far have been processed"); |
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PassingInstructionParserTest.java
| ... | ... | @@ -47,12 +47,11 @@ public class PassingInstructionParserTest extends OnlineTracTracBasedTest { |
| 47 | 47 | @Test |
| 48 | 48 | public void test() { |
| 49 | 49 | int i = 0; |
| 50 | - for(Waypoint w : getRace().getCourse().getWaypoints()){ |
|
| 51 | - if(w.getPassingInstructions()==PassingInstruction.Starboard){ |
|
| 50 | + for (Waypoint w : getRace().getCourse().getWaypoints()) { |
|
| 51 | + if (w.getPassingInstructions() == PassingInstruction.Starboard) { |
|
| 52 | 52 | i++; |
| 53 | 53 | } |
| 54 | 54 | } |
| 55 | - assertEquals(2,i); |
|
| 55 | + assertEquals(2, i); |
|
| 56 | 56 | } |
| 57 | - |
|
| 58 | 57 | } |
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ScoreCorrectionForUntrackedRacesTest.java
| ... | ... | @@ -0,0 +1,112 @@ |
| 1 | +package com.sap.sailing.domain.test; |
|
| 2 | + |
|
| 3 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
|
| 4 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
|
| 5 | +import static org.mockito.Mockito.mock; |
|
| 6 | +import static org.mockito.Mockito.when; |
|
| 7 | + |
|
| 8 | +import java.util.Arrays; |
|
| 9 | +import java.util.UUID; |
|
| 10 | + |
|
| 11 | +import org.junit.jupiter.api.BeforeEach; |
|
| 12 | +import org.junit.jupiter.api.Test; |
|
| 13 | + |
|
| 14 | +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; |
|
| 15 | +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; |
|
| 16 | +import com.sap.sailing.domain.abstractlog.race.RaceLog; |
|
| 17 | +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl; |
|
| 18 | +import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RegisteredCompetitorsAnalyzer; |
|
| 19 | +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl; |
|
| 20 | +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogUseCompetitorsFromRaceLogEventImpl; |
|
| 21 | +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; |
|
| 22 | +import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl; |
|
| 23 | +import com.sap.sailing.domain.base.CompetitorWithBoat; |
|
| 24 | +import com.sap.sailing.domain.base.Fleet; |
|
| 25 | +import com.sap.sailing.domain.base.RaceColumnInSeries; |
|
| 26 | +import com.sap.sailing.domain.base.Series; |
|
| 27 | +import com.sap.sailing.domain.base.impl.FleetImpl; |
|
| 28 | +import com.sap.sailing.domain.base.impl.SeriesImpl; |
|
| 29 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 30 | +import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; |
|
| 31 | +import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl; |
|
| 32 | +import com.sap.sse.common.TimePoint; |
|
| 33 | +import com.sap.sse.common.Util.Pair; |
|
| 34 | + |
|
| 35 | +/** |
|
| 36 | + * See bug6168; tests fleet-specific check for score corrections in race columns that suggest that this |
|
| 37 | + * fleet has finished the race. |
|
| 38 | + * |
|
| 39 | + * @author Axel Uhl (d043530) |
|
| 40 | + * |
|
| 41 | + */ |
|
| 42 | +public class ScoreCorrectionForUntrackedRacesTest extends StoredTrackBasedTest { |
|
| 43 | + private RaceColumnInSeries f1; |
|
| 44 | + private RaceColumnInSeries f2; |
|
| 45 | + private Series series; |
|
| 46 | + private Fleet gold; |
|
| 47 | + private Fleet silver; |
|
| 48 | + private RegattaLog regattaLog; |
|
| 49 | + private RaceLog goldRaceLogF1; |
|
| 50 | + private RaceLog goldRaceLogF2; |
|
| 51 | + private RaceLog silverRaceLogF1; |
|
| 52 | + private RaceLog silverRaceLogF2; |
|
| 53 | + private SettableScoreCorrection scoreCorrection; |
|
| 54 | + private CompetitorWithBoat c1; |
|
| 55 | + private CompetitorWithBoat c2; |
|
| 56 | + private AbstractLogEventAuthor author; |
|
| 57 | + private Leaderboard leaderboard; |
|
| 58 | + |
|
| 59 | + @BeforeEach |
|
| 60 | + public void setUp() { |
|
| 61 | + leaderboard = mock(Leaderboard.class); |
|
| 62 | + c1 = createCompetitorWithBoat("C1"); |
|
| 63 | + c2 = createCompetitorWithBoat("C2"); |
|
| 64 | + author = new LogEventAuthorImpl("Test", 1); |
|
| 65 | + gold = new FleetImpl("Gold", 1); |
|
| 66 | + silver = new FleetImpl("Silver", 2); |
|
| 67 | + series = new SeriesImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(gold, silver), Arrays.asList("F1", "F2"), /* trackedRegattaRegistry */ null); |
|
| 68 | + f1 = mock(RaceColumnInSeries.class); |
|
| 69 | + when(f1.getName()).thenReturn("F1"); |
|
| 70 | + when(f1.getSeries()).thenReturn(series); |
|
| 71 | + when(f1.getKey(c1)).thenReturn(new Pair<>(c1, f1)); |
|
| 72 | + when(f1.getKey(c2)).thenReturn(new Pair<>(c2, f1)); |
|
| 73 | + goldRaceLogF1 = new RaceLogImpl(UUID.randomUUID()); |
|
| 74 | + silverRaceLogF1 = new RaceLogImpl(UUID.randomUUID()); |
|
| 75 | + when(f1.getRaceLog(gold)).thenReturn(goldRaceLogF1); |
|
| 76 | + when(f1.getRaceLog(silver)).thenReturn(silverRaceLogF1); |
|
| 77 | + f2 = mock(RaceColumnInSeries.class); |
|
| 78 | + when(f2.getName()).thenReturn("F2"); |
|
| 79 | + when(f2.getSeries()).thenReturn(series); |
|
| 80 | + when(f2.getKey(c1)).thenReturn(new Pair<>(c1, f2)); |
|
| 81 | + when(f2.getKey(c2)).thenReturn(new Pair<>(c2, f2)); |
|
| 82 | + goldRaceLogF2 = new RaceLogImpl(UUID.randomUUID()); |
|
| 83 | + silverRaceLogF2 = new RaceLogImpl(UUID.randomUUID()); |
|
| 84 | + when(f2.getRaceLog(gold)).thenReturn(goldRaceLogF2); |
|
| 85 | + when(f2.getRaceLog(silver)).thenReturn(silverRaceLogF2); |
|
| 86 | + goldRaceLogF1.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0)); |
|
| 87 | + goldRaceLogF1.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c1)); |
|
| 88 | + goldRaceLogF2.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0)); |
|
| 89 | + goldRaceLogF2.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c1)); |
|
| 90 | + silverRaceLogF1.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0)); |
|
| 91 | + silverRaceLogF1.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c2)); |
|
| 92 | + silverRaceLogF2.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0)); |
|
| 93 | + silverRaceLogF2.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c2)); |
|
| 94 | + regattaLog = new RegattaLogImpl(UUID.randomUUID()); |
|
| 95 | + when(f1.getAllCompetitors(gold)).thenReturn(new RegisteredCompetitorsAnalyzer(goldRaceLogF1, regattaLog).analyze()); |
|
| 96 | + when(f1.getAllCompetitors(silver)).thenReturn(new RegisteredCompetitorsAnalyzer(silverRaceLogF1, regattaLog).analyze()); |
|
| 97 | + when(f2.getAllCompetitors(gold)).thenReturn(new RegisteredCompetitorsAnalyzer(goldRaceLogF2, regattaLog).analyze()); |
|
| 98 | + when(f2.getAllCompetitors(silver)).thenReturn(new RegisteredCompetitorsAnalyzer(silverRaceLogF2, regattaLog).analyze()); |
|
| 99 | + scoreCorrection = new ScoreCorrectionImpl(leaderboard); |
|
| 100 | + scoreCorrection.correctScore(c1, f1, 1.0); |
|
| 101 | + scoreCorrection.correctScore(c1, f2, 1.0); |
|
| 102 | + scoreCorrection.correctScore(c2, f1, 1.0); |
|
| 103 | + } |
|
| 104 | + |
|
| 105 | + @Test |
|
| 106 | + public void testNumberOfRacesWithCorrections() { |
|
| 107 | + assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f1, gold)); |
|
| 108 | + assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f1, silver)); |
|
| 109 | + assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f2, gold)); |
|
| 110 | + assertFalse(scoreCorrection.hasCorrectionForNonTrackedFleet(f2, silver)); |
|
| 111 | + } |
|
| 112 | +} |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java
| ... | ... | @@ -4,6 +4,7 @@ import java.io.Serializable; |
| 4 | 4 | import java.util.concurrent.Callable; |
| 5 | 5 | |
| 6 | 6 | import com.sap.sailing.domain.base.Competitor; |
| 7 | +import com.sap.sailing.domain.base.Fleet; |
|
| 7 | 8 | import com.sap.sailing.domain.base.RaceColumn; |
| 8 | 9 | import com.sap.sailing.domain.common.MaxPointsReason; |
| 9 | 10 | import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; |
| ... | ... | @@ -121,8 +122,9 @@ public interface ScoreCorrection extends Serializable { |
| 121 | 122 | * for any competitor who in <code>raceInLeaderboard</code> is not in a tracked race and hence the fleet assignment |
| 122 | 123 | * cannot be determined. This is helpful, e.g., for progress detection. If score corrections are present for such |
| 123 | 124 | * untracked competitors then all untracked fleets need to be assumed as finished. |
| 125 | + * @param fleet TODO |
|
| 124 | 126 | */ |
| 125 | - boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard); |
|
| 127 | + boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard, Fleet fleet); |
|
| 126 | 128 | |
| 127 | 129 | /** |
| 128 | 130 | * @return all race columns for which this score corrections object has at least one correction; note that this |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java
| ... | ... | @@ -290,7 +290,6 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> { |
| 290 | 290 | } |
| 291 | 291 | } |
| 292 | 292 | } |
| 293 | - // TODO bug5877: pass leaderboard and competitors and a totalPointsSupplier (based on totalPointsCache, see call to compareByBetterScore below) to allow for identifying competitors promoted through to later medal race |
|
| 294 | 293 | int result = scoringScheme.compareByMedalRaceParticipation(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored); |
| 295 | 294 | if (result == 0) { |
| 296 | 295 | result = defaultFleetBasedComparisonResult; |
| ... | ... | @@ -482,29 +481,26 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> { |
| 482 | 481 | } |
| 483 | 482 | |
| 484 | 483 | /** |
| 485 | - * If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if <code>fleet</code> is the |
|
| 486 | - * best fleet with others in the column being worse, return "better" (lesser; -1). If <code>fleet</code> is the worst fleet |
|
| 487 | - * with others in the column being better, return "worse" (greater; 1). Otherwise, return 0. |
|
| 484 | + * If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if there are other fleets with |
|
| 485 | + * a {@link Fleet#getOrdering() rank} different from that of <code>fleet</code>, we want to sort competitors with no fleet |
|
| 486 | + * assignment to the "worse" end of the leaderboard, therefore returning 1. Otherwise, 0 is returned. |
|
| 488 | 487 | */ |
| 489 | 488 | private int extremeFleetComparison(RaceColumn raceColumn, Fleet fleet) { |
| 490 | - boolean allOthersAreGreater = true; |
|
| 491 | - boolean allOthersAreLess = true; |
|
| 489 | + boolean greaterFleetExists = false; |
|
| 490 | + boolean lesserFleetExists = false; |
|
| 492 | 491 | boolean othersExist = false; |
| 493 | 492 | for (Fleet f : raceColumn.getFleets()) { |
| 494 | 493 | if (f != fleet) { |
| 495 | 494 | othersExist = true; |
| 496 | - allOthersAreGreater = allOthersAreGreater && f.compareTo(fleet) > 0; |
|
| 497 | - allOthersAreLess = allOthersAreLess && f.compareTo(fleet) < 0; |
|
| 495 | + greaterFleetExists = greaterFleetExists || f.compareTo(fleet) > 0; |
|
| 496 | + lesserFleetExists = lesserFleetExists || f.compareTo(fleet) < 0; |
|
| 498 | 497 | } |
| 499 | 498 | } |
| 500 | - int result = 0; |
|
| 501 | - if (othersExist) { |
|
| 502 | - assert !(allOthersAreGreater && allOthersAreLess); |
|
| 503 | - if (allOthersAreGreater) { |
|
| 504 | - result = -1; |
|
| 505 | - } else if (allOthersAreLess) { |
|
| 506 | - result = 1; |
|
| 507 | - } |
|
| 499 | + final int result; |
|
| 500 | + if (othersExist && (greaterFleetExists || lesserFleetExists)) { |
|
| 501 | + result = -1; // the competitor with no fleet is considered worse than a competitor in one of the ranked fleets |
|
| 502 | + } else { |
|
| 503 | + result = 0; |
|
| 508 | 504 | } |
| 509 | 505 | return result; |
| 510 | 506 | } |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java
| ... | ... | @@ -14,6 +14,7 @@ import java.util.logging.Logger; |
| 14 | 14 | |
| 15 | 15 | import com.sap.sailing.domain.base.Competitor; |
| 16 | 16 | import com.sap.sailing.domain.base.Course; |
| 17 | +import com.sap.sailing.domain.base.Fleet; |
|
| 17 | 18 | import com.sap.sailing.domain.base.RaceColumn; |
| 18 | 19 | import com.sap.sailing.domain.base.RaceColumnInSeries; |
| 19 | 20 | import com.sap.sailing.domain.common.MaxPointsReason; |
| ... | ... | @@ -610,19 +611,17 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { |
| 610 | 611 | |
| 611 | 612 | @Override |
| 612 | 613 | public boolean hasCorrectionFor(RaceColumn raceInLeaderboard) { |
| 613 | - return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ false); |
|
| 614 | + return internalHasScoreCorrectionFor(raceInLeaderboard); |
|
| 614 | 615 | } |
| 615 | 616 | |
| 616 | - private boolean internalHasScoreCorrectionFor(RaceColumn raceInLeaderboard, boolean considerOnlyUntrackedRaces) { |
|
| 617 | - for (Pair<Competitor, RaceColumn> correctedScoresKey : correctedScores.keySet()) { |
|
| 618 | - if (correctedScoresKey.getB() == raceInLeaderboard && |
|
| 619 | - (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(correctedScoresKey.getA()) == null)) { |
|
| 617 | + private boolean internalHasScoreCorrectionFor(RaceColumn raceInLeaderboard) { |
|
| 618 | + for (final Pair<Competitor, RaceColumn> correctedScoresKey : correctedScores.keySet()) { |
|
| 619 | + if (correctedScoresKey.getB() == raceInLeaderboard) { |
|
| 620 | 620 | return true; |
| 621 | 621 | } |
| 622 | 622 | } |
| 623 | 623 | for (Pair<Competitor, RaceColumn> maxPointsReasonsKey : maxPointsReasons.keySet()) { |
| 624 | - if (maxPointsReasonsKey.getB() == raceInLeaderboard && |
|
| 625 | - (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(maxPointsReasonsKey.getA()) == null)) { |
|
| 624 | + if (maxPointsReasonsKey.getB() == raceInLeaderboard) { |
|
| 626 | 625 | return true; |
| 627 | 626 | } |
| 628 | 627 | } |
| ... | ... | @@ -630,8 +629,21 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { |
| 630 | 629 | } |
| 631 | 630 | |
| 632 | 631 | @Override |
| 633 | - public boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard) { |
|
| 634 | - return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ true); |
|
| 632 | + public boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard, Fleet fleet) { |
|
| 633 | + boolean result; |
|
| 634 | + if (raceInLeaderboard.getTrackedRace(fleet) == null) { |
|
| 635 | + result = false; |
|
| 636 | + for (final Competitor competitor : raceInLeaderboard.getAllCompetitors(fleet)) { |
|
| 637 | + final Pair<Competitor, RaceColumn> key = raceInLeaderboard.getKey(competitor); |
|
| 638 | + if (correctedScores.containsKey(key) || maxPointsReasons.containsKey(key)) { |
|
| 639 | + result = true; |
|
| 640 | + break; |
|
| 641 | + } |
|
| 642 | + } |
|
| 643 | + } else { |
|
| 644 | + result = false; |
|
| 645 | + } |
|
| 646 | + return result; |
|
| 635 | 647 | } |
| 636 | 648 | |
| 637 | 649 | @Override |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackWithRemove.java
| ... | ... | @@ -26,5 +26,5 @@ public interface DynamicTrackWithRemove<FixType extends Timed> extends DynamicTr |
| 26 | 26 | */ |
| 27 | 27 | boolean remove(FixType fix); |
| 28 | 28 | |
| 29 | - void removeAllUpToAndIncluding(FixType fix); |
|
| 29 | + void removeAllUpToExcluding(FixType fix); |
|
| 30 | 30 | } |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackWithRemoveImpl.java
| ... | ... | @@ -27,7 +27,7 @@ public class DynamicTrackWithRemoveImpl<FixType extends Timed> extends DynamicTr |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | 29 | @Override |
| 30 | - public void removeAllUpToAndIncluding(FixType fix) { |
|
| 31 | - getInternalFixes().removeAllLessOrEqual(fix); |
|
| 30 | + public void removeAllUpToExcluding(FixType fix) { |
|
| 31 | + getInternalFixes().removeAllLessThan(fix); |
|
| 32 | 32 | } |
| 33 | 33 | } |
java/com.sap.sailing.geocoding/src/com/sap/sailing/geocoding/impl/ReverseGeocoderImpl.java
| ... | ... | @@ -14,6 +14,7 @@ import java.util.Collections; |
| 14 | 14 | import java.util.Comparator; |
| 15 | 15 | import java.util.Iterator; |
| 16 | 16 | import java.util.List; |
| 17 | +import java.util.Optional; |
|
| 17 | 18 | import java.util.Random; |
| 18 | 19 | import java.util.TimeZone; |
| 19 | 20 | import java.util.logging.Level; |
| ... | ... | @@ -36,7 +37,21 @@ import com.sap.sse.common.Util; |
| 36 | 37 | import com.sap.sse.util.HttpUrlConnectionHelper; |
| 37 | 38 | |
| 38 | 39 | public class ReverseGeocoderImpl implements ReverseGeocoder { |
| 40 | + /** |
|
| 41 | + * The system property name to get the comma separated list of geonames.org usernames; takes precedence over |
|
| 42 | + * the environment variable whose name is given by {@link #USERNAMES_ENV_VARIABLE_NAME}. |
|
| 43 | + */ |
|
| 39 | 44 | private static final String USERNAMES_SYSTEM_PROPERTY_NAME = "geonames.org.usernames"; |
| 45 | + |
|
| 46 | + /** |
|
| 47 | + * The environment variable name to get the comma separated list of geonames.org usernames. Not evaluated |
|
| 48 | + * if the system property whose name is given by {@link #USERNAMES_SYSTEM_PROPERTY_NAME} is set. |
|
| 49 | + */ |
|
| 50 | + private static final String USERNAMES_ENV_VARIABLE_NAME = "GEONAMES_ORG_USERNAMES"; |
|
| 51 | + |
|
| 52 | + /** |
|
| 53 | + * Default username in case neither system property nor environment variable is set. |
|
| 54 | + */ |
|
| 40 | 55 | private static final String GEONAMES_DEFAULT_USER_NAME = "sailtracking0"; |
| 41 | 56 | |
| 42 | 57 | private static final String DATES = "dates"; |
| ... | ... | @@ -63,7 +78,8 @@ public class ReverseGeocoderImpl implements ReverseGeocoder { |
| 63 | 78 | private static final Logger logger = Logger.getLogger(ReverseGeocoderImpl.class.getName()); |
| 64 | 79 | |
| 65 | 80 | public ReverseGeocoderImpl() { |
| 66 | - final String commaSeparatedUsernames = System.getProperty(USERNAMES_SYSTEM_PROPERTY_NAME, GEONAMES_DEFAULT_USER_NAME); |
|
| 81 | + final String commaSeparatedUsernames = System.getProperty(USERNAMES_SYSTEM_PROPERTY_NAME, |
|
| 82 | + Optional.ofNullable(System.getenv(USERNAMES_ENV_VARIABLE_NAME)).orElse(GEONAMES_DEFAULT_USER_NAME)); |
|
| 67 | 83 | this.usernames = commaSeparatedUsernames.split(","); |
| 68 | 84 | } |
| 69 | 85 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
| ... | ... | @@ -8,6 +8,11 @@ |
| 8 | 8 | <h5 class="articleSubheadline">October 2025</h5> |
| 9 | 9 | <ul class="bulletList"> |
| 10 | 10 | <li>Bug fix: the "Edit Mark Passings" panel showed only up to 15 waypoints.</li> |
| 11 | + <li>Competitors not assigned to any fleet will no longer be sorted in between a Gold |
|
| 12 | + and a Silver fleet but will end up "at the bottom" of the leaderboard.</li> |
|
| 13 | + <li>Bug fix: when a split-fleet series had results only entered manually as score |
|
| 14 | + corrections, the progress bars for the series were not shown properly. All |
|
| 15 | + fleets would be considered finished although some may not have seen results.</li> |
|
| 11 | 16 | </ul> |
| 12 | 17 | <h5 class="articleSubheadline">September 2025</h5> |
| 13 | 18 | <ul class="bulletList"> |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java
| ... | ... | @@ -585,7 +585,8 @@ public class RaceContext { |
| 585 | 585 | } |
| 586 | 586 | } |
| 587 | 587 | ScoreCorrection scoreCorrection = leaderboard.getScoreCorrection(); |
| 588 | - if (trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn)) { |
|
| 588 | + // bug6168: for split fleets we need to check the fleet too |
|
| 589 | + if (trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn, fleet)) { |
|
| 589 | 590 | return RaceViewState.FINISHED; |
| 590 | 591 | } |
| 591 | 592 | if (startTime != null) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java
| ... | ... | @@ -341,6 +341,7 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race |
| 341 | 341 | contentPanel.add(new Label(stringMessages.seeIgtimiTabForAccountSettings())); |
| 342 | 342 | final CheckBox correctByDeclination = new CheckBox(stringMessages.declinationCheckbox()); |
| 343 | 343 | correctByDeclination.setValue(true); // by default this is desirable because the Igtimi connector reads uncorrected magnetic values |
| 344 | + final TextBox optionalBearerTokenBox = new TextBox(); |
|
| 344 | 345 | final Button importButton = new Button(stringMessages.importWindFromIgtimi()); |
| 345 | 346 | importButton.ensureDebugId("ImportWindFromIgtimi"); |
| 346 | 347 | final HTML resultReport = new HTML(); |
| ... | ... | @@ -357,7 +358,9 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race |
| 357 | 358 | if (Window.confirm(warningMessage)) { |
| 358 | 359 | resultReport.setText(stringMessages.loading()); |
| 359 | 360 | sailingServiceWrite.importWindFromIgtimi(new ArrayList<>(refreshableRaceSelectionModel.getSelectedSet()), |
| 360 | - correctByDeclination.getValue(), new AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>>() { |
|
| 361 | + correctByDeclination.getValue(), |
|
| 362 | + Util.hasLength(optionalBearerTokenBox.getValue()) ? optionalBearerTokenBox.getValue().trim() : null, |
|
| 363 | + new AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>>() { |
|
| 361 | 364 | @Override |
| 362 | 365 | public void onFailure(Throwable caught) { |
| 363 | 366 | errorReporter.reportError(stringMessages.errorImportingIgtimiWind(caught.getMessage())); |
| ... | ... | @@ -383,6 +386,11 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race |
| 383 | 386 | } |
| 384 | 387 | }); |
| 385 | 388 | contentPanel.add(correctByDeclination); |
| 389 | + final HorizontalPanel tokenPanel = new HorizontalPanel(); |
|
| 390 | + tokenPanel.setSpacing(5); |
|
| 391 | + tokenPanel.add(new Label(stringMessages.optionalBearerTokenForWindImport())); |
|
| 392 | + tokenPanel.add(optionalBearerTokenBox); |
|
| 393 | + contentPanel.add(tokenPanel); |
|
| 386 | 394 | contentPanel.add(importButton); |
| 387 | 395 | contentPanel.add(resultReport); |
| 388 | 396 | return igtimiWindImportRootPanel; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceWrite.java
| ... | ... | @@ -243,7 +243,7 @@ public interface SailingServiceWrite extends FileStorageManagementGwtService, Sa |
| 243 | 243 | throws NotFoundException, NotDenotableForRaceLogTrackingException; |
| 244 | 244 | |
| 245 | 245 | Map<RegattaAndRaceIdentifier, Integer> importWindFromIgtimi(List<RaceDTO> selectedRaces, |
| 246 | - boolean correctByDeclination) |
|
| 246 | + boolean correctByDeclination, String optionalBearerTokenOrNull) |
|
| 247 | 247 | throws IllegalStateException, Exception; |
| 248 | 248 | |
| 249 | 249 | IgtimiDataAccessWindowWithSecurityDTO addIgtimiDataAccessWindow(String deviceSerialNumber, Date from, Date to); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceWriteAsync.java
| ... | ... | @@ -586,7 +586,7 @@ public interface SailingServiceWriteAsync extends FileStorageManagementGwtServic |
| 586 | 586 | void removeIgtimiDevice(String deviceSerialNumber, AsyncCallback<Void> asyncCallback); |
| 587 | 587 | |
| 588 | 588 | void importWindFromIgtimi(List<RaceDTO> selectedRaces, boolean correctByDeclination, |
| 589 | - AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>> asyncCallback); |
|
| 589 | + String optionalBearerTokenOrNull, AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>> asyncCallback); |
|
| 590 | 590 | |
| 591 | 591 | /** |
| 592 | 592 | * The boolean result reflects whether a connection to the device identified by {@code serialNumber} |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
| ... | ... | @@ -2523,4 +2523,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, |
| 2523 | 2523 | String selectToRaceColumn(); |
| 2524 | 2524 | String exportTWAHistogramToCsv(); |
| 2525 | 2525 | String exportWindSpeedHistogramToCsv(); |
| 2526 | + String optionalBearerTokenForWindImport(); |
|
| 2526 | 2527 | } |
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
| ... | ... | @@ -2558,4 +2558,5 @@ successfullyCopiedPairings=Successfully copied pairings |
| 2558 | 2558 | selectFromRaceColumn=Select race column from where to start copying pairings |
| 2559 | 2559 | selectToRaceColumn=Select race colunm to where to copy pairings |
| 2560 | 2560 | exportTWAHistogramToCsv=Export True Wind Angle histogram to CSV |
| 2561 | -exportWindSpeedHistogramToCsv=Export Wind Speed histogram to CSV |
|
| ... | ... | \ No newline at end of file |
| 0 | +exportWindSpeedHistogramToCsv=Export Wind Speed histogram to CSV |
|
| 1 | +optionalBearerTokenForWindImport=Optional bearer token for wind import |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
| ... | ... | @@ -2552,4 +2552,5 @@ successfullyCopiedPairings=Zuordnungen erfolgreich kopiert |
| 2552 | 2552 | selectFromRaceColumn=Spalte auswählen, ab der die Zuordnungen kopiert werden sollen |
| 2553 | 2553 | selectToRaceColumn=Spalte auswählen, bis zu der die Zuordnungen kopiert werden sollen |
| 2554 | 2554 | exportTWAHistogramToCsv=Histogramm der wahren Windwinkel in CSV exportieren |
| 2555 | -exportWindSpeedHistogramToCsv=Histogramm der wahren Windgeschwindigkeiten in CSV exportieren |
|
| ... | ... | \ No newline at end of file |
| 0 | +exportWindSpeedHistogramToCsv=Histogramm der wahren Windgeschwindigkeiten in CSV exportieren |
|
| 1 | +optionalBearerTokenForWindImport=Optionales Bearer Token für Wind-Import |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java
| ... | ... | @@ -1,5 +1,7 @@ |
| 1 | 1 | package com.sap.sailing.gwt.ui.server; |
| 2 | 2 | |
| 3 | +import java.util.Optional; |
|
| 4 | + |
|
| 3 | 5 | import org.osgi.framework.BundleActivator; |
| 4 | 6 | import org.osgi.framework.BundleContext; |
| 5 | 7 | |
| ... | ... | @@ -10,11 +12,31 @@ public class Activator implements BundleActivator { |
| 10 | 12 | private SailingServiceImpl sailingServiceToStopWhenStopping; |
| 11 | 13 | private static Activator INSTANCE; |
| 12 | 14 | |
| 15 | + /** |
|
| 16 | + * If the system property named after this constant is set, its value is used for Google Maps API authentication. |
|
| 17 | + * It takes precedence over the environment variable named after {@link #GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME}. |
|
| 18 | + */ |
|
| 13 | 19 | private final static String GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME = "google.maps.authenticationparams"; |
| 14 | 20 | |
| 21 | + /** |
|
| 22 | + * If the system property named after {@link #GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME} is not set, |
|
| 23 | + * this environment variable is checked for Google Maps API authentication parameters. |
|
| 24 | + */ |
|
| 25 | + private final static String GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME = "GOOGLE_MAPS_AUTHENTICATION_PARAMS"; |
|
| 26 | + |
|
| 27 | + /** |
|
| 28 | + * The system property named after this constant is expected to provide the YouTube V3 API key. Takes precedence over |
|
| 29 | + * the environment variable named after {@link #YOUTUBE_V3_API_KEY_ENV_VAR_NAME}. |
|
| 30 | + */ |
|
| 15 | 31 | private final static String YOUTUBE_V3_API_KEY_PROPERTY_NAME = "youtube.api.key"; |
| 16 | 32 | |
| 17 | 33 | /** |
| 34 | + * If the system property named after {@link #YOUTUBE_V3_API_KEY_PROPERTY_NAME} is not set, this environment variable |
|
| 35 | + * is checked for the YouTube V3 API key. |
|
| 36 | + */ |
|
| 37 | + private final static String YOUTUBE_V3_API_KEY_ENV_VAR_NAME = "YOUTUBE_V3_API_KEY"; |
|
| 38 | + |
|
| 39 | + /** |
|
| 18 | 40 | * Required by {@link GoogleMapsLoader#load(Runnable, String)} and to be provided through a system property named |
| 19 | 41 | * after {@link GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME}. The value would be something like |
| 20 | 42 | * {@code client=abcde&channel=fghij}. |
| ... | ... | @@ -34,8 +56,12 @@ public class Activator implements BundleActivator { |
| 34 | 56 | @Override |
| 35 | 57 | public void start(BundleContext context) throws Exception { |
| 36 | 58 | Activator.context = context; |
| 37 | - googleMapsLoaderAuthenticationParams = context.getProperty(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME); |
|
| 38 | - youtubeApiKey = context.getProperty(YOUTUBE_V3_API_KEY_PROPERTY_NAME); |
|
| 59 | + googleMapsLoaderAuthenticationParams = Optional |
|
| 60 | + .ofNullable(context.getProperty(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME)) |
|
| 61 | + .orElse(System.getenv(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME)); |
|
| 62 | + youtubeApiKey = Optional |
|
| 63 | + .ofNullable(context.getProperty(YOUTUBE_V3_API_KEY_PROPERTY_NAME)) |
|
| 64 | + .orElse(System.getenv(YOUTUBE_V3_API_KEY_ENV_VAR_NAME)); |
|
| 39 | 65 | } |
| 40 | 66 | |
| 41 | 67 | @Override |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
| ... | ... | @@ -6098,7 +6098,17 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet |
| 6098 | 6098 | return Activator.getInstance().getGoogleMapsLoaderAuthenticationParams(); |
| 6099 | 6099 | } |
| 6100 | 6100 | |
| 6101 | - protected IgtimiConnection createIgtimiConnection() { |
|
| 6102 | - return getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName()) : null); |
|
| 6101 | + /** |
|
| 6102 | + * @param optionalBearerToken |
|
| 6103 | + * if present, this bearer token is used to authenticate the Igtimi connection; otherwise, we try to use |
|
| 6104 | + * Igtimi default credentials provided at startup through the {@code igtimi.bearer.token} system |
|
| 6105 | + * property. Only if no such system property has been set, we look for a logged-in user in the |
|
| 6106 | + * {@link #getSecurityService() security service} and use its access token |
|
| 6107 | + */ |
|
| 6108 | + protected IgtimiConnection createIgtimiConnection(Optional<String> optionalBearerToken) { |
|
| 6109 | + return optionalBearerToken.map(bearerToken->getIgtimiConnectionFactory().getOrCreateConnection(bearerToken)) |
|
| 6110 | + .orElse(getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null |
|
| 6111 | + ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName()) |
|
| 6112 | + : null)); |
|
| 6103 | 6113 | } |
| 6104 | 6114 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceWriteImpl.java
| ... | ... | @@ -2215,7 +2215,7 @@ public class SailingServiceWriteImpl extends SailingServiceImpl implements Saili |
| 2215 | 2215 | |
| 2216 | 2216 | @Override |
| 2217 | 2217 | public Map<RegattaAndRaceIdentifier, Integer> importWindFromIgtimi(List<RaceDTO> selectedRaces, |
| 2218 | - boolean correctByDeclination) |
|
| 2218 | + boolean correctByDeclination, String optionalBearerTokenOrNull) |
|
| 2219 | 2219 | throws IllegalStateException, ClientProtocolException, IOException, org.json.simple.parser.ParseException { |
| 2220 | 2220 | final List<DynamicTrackedRace> trackedRaces = new ArrayList<>(); |
| 2221 | 2221 | if (selectedRaces != null && !selectedRaces.isEmpty()) { |
| ... | ... | @@ -2238,7 +2238,7 @@ public class SailingServiceWriteImpl extends SailingServiceImpl implements Saili |
| 2238 | 2238 | } |
| 2239 | 2239 | } |
| 2240 | 2240 | Map<RegattaAndRaceIdentifier, Integer> numberOfWindFixesImportedPerRace = new HashMap<RegattaAndRaceIdentifier, Integer>(); |
| 2241 | - final IgtimiConnection conn = createIgtimiConnection(); |
|
| 2241 | + final IgtimiConnection conn = createIgtimiConnection(Optional.ofNullable(optionalBearerTokenOrNull)); |
|
| 2242 | 2242 | // filter account based on used permissions to read account: |
| 2243 | 2243 | Map<TrackedRace, Integer> resultsForAccounts = conn.importWindIntoRace(trackedRaces, correctByDeclination); |
| 2244 | 2244 | for (Entry<TrackedRace, Integer> resultForAccount : resultsForAccounts.entrySet()) { |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumTestInvocationProvider.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.selenium.core; |
| 2 | 2 | |
| 3 | 3 | import java.util.Arrays; |
| 4 | 4 | import java.util.List; |
| 5 | +import java.util.Map.Entry; |
|
| 5 | 6 | import java.util.stream.Stream; |
| 6 | 7 | |
| 7 | 8 | import org.junit.jupiter.api.extension.Extension; |
| ... | ... | @@ -33,6 +34,9 @@ public class SeleniumTestInvocationProvider implements TestTemplateInvocationCon |
| 33 | 34 | @Override |
| 34 | 35 | public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) { |
| 35 | 36 | TestEnvironmentConfiguration config = TestEnvironmentConfiguration.getInstance(); |
| 37 | + for (Entry<String, String> e : config.getSystemProperties().entrySet()) { |
|
| 38 | + System.setProperty(e.getKey(), e.getValue()); |
|
| 39 | + } |
|
| 36 | 40 | return config.getDriverDefinitions().stream().map(driverDef -> { |
| 37 | 41 | return new SeleniumTestContext(driverDef); |
| 38 | 42 | }); |
| ... | ... | @@ -55,4 +59,4 @@ public class SeleniumTestInvocationProvider implements TestTemplateInvocationCon |
| 55 | 59 | return Arrays.asList(new SeleniumTestEnvironmentInjector(driverDefinition)); |
| 56 | 60 | } |
| 57 | 61 | } |
| 58 | -} |
|
| ... | ... | \ No newline at end of file |
| 0 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SimulatorOverlayTest.java
| ... | ... | @@ -119,7 +119,7 @@ public class SimulatorOverlayTest extends AbstractSeleniumTest { |
| 119 | 119 | gpxInputStream.close(); |
| 120 | 120 | final String routeconverterWindFileName = tmpFile.getAbsolutePath(); |
| 121 | 121 | windPanel.importWindFromRouteconverter(routeconverterWindFileName, /* waiting up to 10 min */ 15 * 60); |
| 122 | - Thread.sleep(10000); // wait for 10s to allow all wind fixes to get processed by the PolarDataService |
|
| 122 | + Thread.sleep(30000); // wait for 30s to allow all wind fixes to get processed by the PolarDataService |
|
| 123 | 123 | } finally { |
| 124 | 124 | tmpFile.delete(); |
| 125 | 125 | } |
java/com.sap.sailing.www/release_notes_admin.html
| ... | ... | @@ -23,6 +23,21 @@ |
| 23 | 23 | <div class="mainContent"> |
| 24 | 24 | <h2 class="releaseHeadline">Release Notes - Administration Console</h2> |
| 25 | 25 | <div class="innerContent"> |
| 26 | + <h2 class="articleSubheadline">October 2025</h2> |
|
| 27 | + <ul class="bulletList"> |
|
| 28 | + <li>When WindBots sent buffered data, e.g., from previous days, there was a possibility that |
|
| 29 | + under some circumstances this data may have interfered with live data, leading, e.g., |
|
| 30 | + to "jumping" WindBot positions. This has now been fixed.<p> |
|
| 31 | + <li>For the following system properties, alternative environment variables have been introduced: |
|
| 32 | + <ul> |
|
| 33 | + <li><tt>AWS_S3_TEST_S3ACCESSID</tt> for <tt>aws.s3.test.s3AccessId</tt></li> |
|
| 34 | + <li><tt>AWS_S3_TEST_S3ACCESSKEY</tt> for <tt>aws.s3.test.s3AccessKey</tt></li> |
|
| 35 | + <li><tt>GOOGLE_MAPS_AUTHENTICATION_PARAMS</tt> for <tt>google.maps.authenticationparams</tt></li> |
|
| 36 | + <li><tt>YOUTUBE_V3_API_KEY</tt> for <tt>youtube.api.key</tt></li> |
|
| 37 | + <li><tt>GEONAMES_ORG_USERNAMES</tt> for <tt>geonames.org.usernames</tt></li> |
|
| 38 | + </ul> |
|
| 39 | + If the system property is set, it takes precedence over the environment variable.</li> |
|
| 40 | + </ul> |
|
| 26 | 41 | <h2 class="articleSubheadline">September 2025</h2> |
| 27 | 42 | <ul class="bulletList"> |
| 28 | 43 | <li>Added boat classes "Elan E4" and "Beneteau First 36" to the list of boat classes.<p> |
java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java
| ... | ... | @@ -8,17 +8,20 @@ import com.sap.sse.filestorage.impl.BaseFileStorageServiceImpl; |
| 8 | 8 | import com.sap.sse.security.SecurityService; |
| 9 | 9 | |
| 10 | 10 | /** |
| 11 | - * Provide the S3 credentials for an IAM account that has access to the "sapsailing-automatic-upload-test" bucket |
|
| 12 | - * in the system properties {@code aws.s3.test.s3AccessId} and {@code aws.s3.test.s3AccessKey}. For the build |
|
| 13 | - * script in {@code configuration/buildAndUpdateProduct.sh} this can be done by setting the {@code APP_PARAMETERS} |
|
| 14 | - * environment variable for the script like this: {@code APP_PARAMETERS="-Daws.s3.test.s3AccessId=... -Daws.s3.test.s3AccessKey=..."} |
|
| 11 | + * Provide the S3 credentials for an IAM account that has access to the "sapsailing-automatic-upload-test" bucket in the |
|
| 12 | + * system properties {@code aws.s3.test.s3AccessId} and {@code aws.s3.test.s3AccessKey}. For the build script in |
|
| 13 | + * {@code configuration/buildAndUpdateProduct.sh} this can be done by setting the {@code APP_PARAMETERS} environment |
|
| 14 | + * variable for the script like this: |
|
| 15 | + * {@code APP_PARAMETERS="-Daws.s3.test.s3AccessId=... -Daws.s3.test.s3AccessKey=..."}. Alternatively, e.g., in order to |
|
| 16 | + * avoid system property setting with the "-D" command line option to be shown in log files, you may pass the ID and key |
|
| 17 | + * as environment variables {@code AWS_S3_TEST_S3ACCESSID} and {@code AWS_S3_TEST_S3ACCESSKEY}, respectively. |
|
| 15 | 18 | * |
| 16 | 19 | * @author Axel Uhl (d043530) |
| 17 | 20 | * |
| 18 | 21 | */ |
| 19 | 22 | public class AmazonS3TestSupport { |
| 20 | - public static final String s3AccessId = System.getProperty("aws.s3.test.s3AccessId"); |
|
| 21 | - public static final String s3AccessKey = System.getProperty("aws.s3.test.s3AccessKey"); |
|
| 23 | + public static final String s3AccessId = System.getProperty("aws.s3.test.s3AccessId", System.getenv("AWS_S3_TEST_S3ACCESSID")); |
|
| 24 | + public static final String s3AccessKey = System.getProperty("aws.s3.test.s3AccessKey", System.getenv("AWS_S3_TEST_S3ACCESSKEY")); |
|
| 22 | 25 | private static final String s3BucketName = "sapsailing-automatic-upload-test"; |
| 23 | 26 | |
| 24 | 27 | public static BaseFileStorageServiceImpl createService(final SecurityService securityService) throws InvalidPropertiesException, IOException { |
java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/AwsLandscapeImpl.java
| ... | ... | @@ -155,6 +155,7 @@ import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTarg |
| 155 | 155 | import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetGroupsResponse; |
| 156 | 156 | import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthRequest; |
| 157 | 157 | import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthResponse; |
| 158 | +import software.amazon.awssdk.services.elasticloadbalancingv2.model.IpAddressType; |
|
| 158 | 159 | import software.amazon.awssdk.services.elasticloadbalancingv2.model.Listener; |
| 159 | 160 | import software.amazon.awssdk.services.elasticloadbalancingv2.model.LoadBalancer; |
| 160 | 161 | import software.amazon.awssdk.services.elasticloadbalancingv2.model.LoadBalancerAttribute; |
| ... | ... | @@ -348,6 +349,7 @@ public class AwsLandscapeImpl<ShardingKey> implements AwsLandscape<ShardingKey> |
| 348 | 349 | final CreateLoadBalancerResponse response = client |
| 349 | 350 | .createLoadBalancer(CreateLoadBalancerRequest.builder() |
| 350 | 351 | .name(name) |
| 352 | + .ipAddressType(IpAddressType.DUALSTACK) // IPv4 and IPv6 |
|
| 351 | 353 | .subnetMappings(subnetMappings) |
| 352 | 354 | .securityGroups(getDefaultSecurityGroupForApplicationLoadBalancer(region).getId()) |
| 353 | 355 | .build()); |
java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/AwsApplicationConfiguration.java
| ... | ... | @@ -58,9 +58,6 @@ implements UserDataProvider { |
| 58 | 58 | * <li>The {@link #setMailSmtpPort(int) SMTP port} defaults to 25.</li> |
| 59 | 59 | * <li>The {@link #setMailSmtpHost(String) mail SMTP host} defaults to <tt>email-smtp.${region}.amazonaws.com</tt>.</li> |
| 60 | 60 | * <li>The {@link #setMailSmtpAuth(boolean) mail SMTP authentication} is activated by default.</li> |
| 61 | - * <li>The {@link #setMailSmtpUser(String) mail SMTP user} defaults to {@code AKIAIHCQEFAZDLIK7SUQ} which is the |
|
| 62 | - * project's AWS SES (Simple e-Mail Service) username. The {@link #setMailSmtpPassword(String) password}, however, |
|
| 63 | - * must explicitly be provided and does not default to any non-{@code null}, non-empty value.</li> |
|
| 64 | 61 | * <li>The {@link #setMemoryInMegabytes(int) memory size} allocated to the process defaults to what {@link #setMemoryTotalSizeFactor(int)} |
| 65 | 62 | * sets, or, if {@link #setMemoryTotalSizeFactor(int)} isn't used either, to 3/4 of the physical memory available |
| 66 | 63 | * on the host running the application, minus some baseline allocated to the operating system.</li> |
| ... | ... | @@ -148,7 +145,6 @@ implements UserDataProvider { |
| 148 | 145 | * The pattern requires the region ID as {@link String} parameter and produces an AWS SES SMTP hostname for that region. |
| 149 | 146 | */ |
| 150 | 147 | private static final String DEFAULT_SMTP_HOSTNAME_PATTERN = "email-smtp.%s.amazonaws.com"; |
| 151 | - private static final String DEFAULT_SMTP_USER = "AKIAIHCQEFAZDLIK7SUQ"; |
|
| 152 | 148 | private static final Integer DEFAULT_SMTP_PORT = 25; |
| 153 | 149 | private AwsLandscape<ShardingKey> landscape; |
| 154 | 150 | private AwsRegion region; |
| ... | ... | @@ -388,7 +384,9 @@ implements UserDataProvider { |
| 388 | 384 | userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_HOST, mailSmtpHost==null?getDefaultAwsSesMailHostForRegion():mailSmtpHost); |
| 389 | 385 | userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_PORT, mailSmtpPort==null?DEFAULT_SMTP_PORT.toString():mailSmtpPort.toString()); |
| 390 | 386 | userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_AUTH, mailSmtpAuth==null?Boolean.TRUE.toString():mailSmtpAuth.toString()); |
| 391 | - userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_USER, mailSmtpUser==null?DEFAULT_SMTP_USER:mailSmtpUser); |
|
| 387 | + if (mailSmtpUser != null) { |
|
| 388 | + userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_USER, mailSmtpUser); |
|
| 389 | + } |
|
| 392 | 390 | if (mailSmtpPassword != null) { |
| 393 | 391 | userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_PASSWORD, mailSmtpPassword); |
| 394 | 392 | } |
wiki/howto/onboarding.md
| ... | ... | @@ -10,7 +10,7 @@ First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements |
| 10 | 10 | |
| 11 | 11 | 1. Git Account |
| 12 | 12 | |
| 13 | - - The primary Git repository for the project is hosted on Github (see [https://github.com/SAP/sailing-analytics](https://github.com/SAP/sailing-analytics)). To clone, use ``git@github.com:SAP/sailing-analytics.git``. To gain write access you have to become member of the [sailing-analytics-team](https://github.com/orgs/SAP/teams/sailing-analytics-team) organization. For that you need to [link your Github user to the Github SAP organization](https://wiki.one.int.sap/wiki/display/ospodocs/Self-Service+for+Joining+an+SAP+GitHub+Organization). We still have a shadow repository around that, e.g., powers our Wiki at [https://wiki.sapsailing.com](https://wiki.sapsailing.com) and which lives at ``ssh://trac@sapsailing.com/home/trac/git``. |
|
| 13 | + - The primary Git repository for the project is hosted on Github (see [https://github.com/SAP/sailing-analytics](https://github.com/SAP/sailing-analytics)). To clone, use ``git@github.com:SAP/sailing-analytics.git``. To gain write access you have to become member of the [sailing-analytics-team](https://github.com/orgs/SAP/teams/sailing-analytics-team) organization. For that you need to [link your Github user to the Github SAP organization](https://wiki.one.int.sap/wiki/display/ospodocs/Self-Service+for+Joining+an+SAP+GitHub+Organization). For that to work, your Github account needs to have your @sap.com e-mail address assigned and verified. We still have a shadow repository around that, e.g., powers our Wiki at [https://wiki.sapsailing.com](https://wiki.sapsailing.com) and which lives at ``ssh://trac@sapsailing.com/home/trac/git``. |
|
| 14 | 14 | |
| 15 | 15 | - In case you'd like to get access to the external git at `ssh://trac@sapsailing.com/home/trac/git` please send your SSH public key to one of the project maintainers, requesting git access. Make sure to NOT generate the key using Putty. Putty keys don't work reliably under Linux and on Windows/Cygwin environments. Use ssh-keygen in a Cygwin or Linux or MacOS/X environment instead. For further instructions for generating an ssh-key see [GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). |
| 16 | 16 | Note: If you want to use the ssh-key in the context of our solution, it can be an RSA or ED25519 format. Example for creating a key: `ssh-keygen -t ed25519 -b 512 -C "test@test.com"`. Make sure to set a non-empty password for your key. |
wiki/info/landscape/operating-system-upgrade.md
| ... | ... | @@ -28,7 +28,7 @@ Amazon Linux 2023 uses ``dnf`` as its package manager. An operating system upgra |
| 28 | 28 | ``` |
| 29 | 29 | dnf --releasever=latest upgrade |
| 30 | 30 | ``` |
| 31 | -This will upgrade all packages installed as well as the kernel. When run interactively, upgrade requiring a reboot will be displayed in the update list in red color. For scripted use, consider the ``needs-restarting`` command, delivering an exit status of ``1`` if a reboot is required. |
|
| 31 | +This will upgrade all packages installed as well as the kernel. When run interactively, upgrade requiring a reboot will be displayed in the update list in red color. For scripted use, consider the ``needs-restarting -r`` command, delivering an exit status of ``1`` if a reboot is required. |
|
| 32 | 32 | |
| 33 | 33 | #### Debian |
| 34 | 34 |