Merge branch 'main' into bug6239

This commit is contained in:
masha.kashirina
2026-05-23 15:31:27 +02:00
12 changed files with 195 additions and 125 deletions
+2 -2
View File
@@ -75,8 +75,8 @@ jobs:
tags: ghcr.io/${{steps.ghcr.outputs.PACKAGE}}:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-17', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-21', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-24', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-25' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-25', steps.ghcr.outputs.PACKAGE) || '' }}
annotations: |
maintainer=axel.uhl@sap.com
index:org.opencontainers.image.title=Sailing Analytics
index:org.opencontainers.image.description=The Sailing Analytics Web Application
index:org.opencontainers.image.title=Eclipse Azimuth Sailing Analytics
index:org.opencontainers.image.description=The Eclipse Azimuth Sailing Analytics Web Application
file: ${{ env.BRANCH == 'docker-17' && 'docker/Dockerfile_sapsailing_on_sapmachine17' || env.BRANCH == 'docker-21' && 'docker/Dockerfile_sapsailing_on_sapmachine21' || env.BRANCH == 'docker-24' && 'docker/Dockerfile_sapsailing_on_sapmachine24' || env.BRANCH == 'docker-25' && 'docker/Dockerfile_sapsailing_on_sapmachine25' || 'docker/Dockerfile' }}
context: docker/
platforms: linux/amd64,linux/arm64
+2 -1
View File
@@ -3,7 +3,8 @@ source "https://rubygems.org"
platform :ruby do
gem 'highline', '> 0'
gem 'json', '> 0'
gem 'fastlane', '> 0'
gem 'fastlane', '>= 2.222.0' # This version and later support jwt 3.x
gem 'googleauth', '>= 1.11.0' # Explicitly require version that supports jwt 3.x
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
+59 -61
View File
@@ -1,18 +1,15 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
CFPropertyList (3.0.8)
abbrev (0.1.2)
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1198.0)
aws-sdk-core (3.240.0)
aws-partitions (1.1250.0)
aws-sdk-core (3.247.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -20,19 +17,19 @@ GEM
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.118.0)
aws-sdk-core (~> 3, >= 3.239.1)
aws-sdk-kms (1.125.0)
aws-sdk-core (~> 3, >= 3.247.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.208.0)
aws-sdk-core (~> 3, >= 3.234.0)
aws-sdk-s3 (1.222.0)
aws-sdk-core (~> 3, >= 3.247.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (4.0.1)
bigdecimal (4.1.2)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
@@ -58,31 +55,31 @@ GEM
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday-cookie_jar (0.0.8)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
http-cookie (>= 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-em_synchrony (1.0.1)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.1.0)
faraday-multipart (1.2.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday-retry (1.0.4)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.232.2)
CFPropertyList (>= 2.3, < 4.0.0)
abbrev (~> 0.1.2)
fastimage (2.4.1)
fastlane (2.234.0)
CFPropertyList (>= 2.3, < 5.0.0)
abbrev (~> 0.1)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.197)
babosa (>= 1.0.3, < 2.0.0)
base64 (~> 0.2.0)
base64 (~> 0.2)
benchmark (>= 0.1.0)
bundler (>= 1.17.3, < 5.0.0)
colored (~> 1.2)
@@ -95,7 +92,7 @@ GEM
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0)
fastlane-sirp (>= 1.1.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
@@ -108,9 +105,9 @@ GEM
logger (>= 1.6, < 2.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
mutex_m (~> 0.3.0)
mutex_m (~> 0.3)
naturally (~> 2.2)
nkf (~> 0.2.0)
nkf (~> 0.2)
optparse (>= 0.1.1, < 1.0.0)
ostruct (>= 0.1.0)
plist (>= 3.1.0, < 4.0.0)
@@ -128,41 +125,42 @@ GEM
fastlane-plugin-json (1.1.7)
fastlane-plugin-properties (1.1.2)
java-properties
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
fastlane-sirp (1.1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
google-apis-androidpublisher_v3 (0.100.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
googleauth (~> 1.9)
httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.7.1)
google-apis-iamcredentials_v1 (0.27.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-playcustomapp_v1 (0.17.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.62.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.4.0)
google-cloud-storage (1.47.0)
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.6.0)
google-cloud-storage (1.60.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.31.0)
google-apis-core (>= 0.18, < 2)
google-apis-iamcredentials_v1 (~> 0.18)
google-apis-storage_v1 (>= 0.42)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
googleauth (~> 1.9)
mini_mime (~> 1.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
googleauth (1.11.2)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
@@ -174,43 +172,42 @@ GEM
mutex_m
java-properties (0.3.0)
jmespath (1.6.2)
json (2.11.1)
jwt (2.10.1)
json (2.19.5)
jwt (2.10.2)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
multi_json (1.21.1)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.2.1)
naturally (2.3.0)
nkf (0.2.0)
optparse (0.6.0)
optparse (0.8.1)
os (1.1.4)
ostruct (0.6.3)
plist (3.7.2)
public_suffix (7.0.5)
rake (13.2.1)
rake (13.4.2)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.4.2)
retriable (3.4.1)
rexml (3.4.4)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
security (0.1.5)
signet (0.19.0)
signet (0.21.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
jwt (>= 1.5, < 4.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
@@ -238,9 +235,10 @@ PLATFORMS
ruby
DEPENDENCIES
fastlane (> 0)
fastlane (>= 2.222.0)
fastlane-plugin-json
fastlane-plugin-properties
googleauth (>= 1.11.0)
highline (> 0)
json (> 0)
@@ -1,9 +1,11 @@
package com.sap.sailing.declination.test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import com.sap.sailing.declination.impl.NOAAImporterForTesting;
@Disabled("Disabled because NOAA server seems down; 2026-05-19")
public class NOAADeclinationImportTest extends DeclinationImportTest<NOAAImporterForTesting> {
@BeforeEach
public void setUp() {
@@ -1,9 +1,11 @@
package com.sap.sailing.declination.test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import com.sap.sailing.declination.impl.NOAAImporter;
@Disabled("Disabled because NOAA server seems down; 2026-05-19")
public class NOAASimpleDeclinationTest extends SimpleDeclinationTest<NOAAImporter> {
@BeforeEach
public void setUp() {
@@ -4,10 +4,9 @@ import java.util.ArrayList;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog;
import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
import com.sap.sailing.gwt.ui.adminconsole.AbstractLeaderboardConfigPanel.RaceColumnDTOAndFleetDTOWithNameBasedEquality;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.gwt.client.IconResources;
import com.sap.sse.gwt.client.celltable.ImagesBarCell;
@@ -60,13 +59,12 @@ public class RaceLogTrackingEventManagementRaceImagesBarCell extends ImagesBarCe
result.add(new ImageSpec(ACTION_SET_FINISHING_AND_FINISH_TIME, stringMessages.setFinishingAndFinishTime(), makeImagePrototype(resources.blueSmall())));
result.add(new ImageSpec(ACTION_SHOW_RACELOG, stringMessages.raceLog(), makeImagePrototype(resources.flagIcon())));
result.add(new ImageSpec(ACTION_SET_TRACKING_TIMES, stringMessages.setTrackingTimes(), makeImagePrototype(resources.setTrackingTimes())));
Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog> startEndTrackingTime = smartphoneTrackingEventManagementPanel.getTrackingTimesFor(object);
if (startEndTrackingTime == null) {
final boolean trackerExists = object.getA().getRaceLogTrackingInfo(object.getB()).raceLogTrackerExists;
final RaceLogTrackingState trackingState = object.getA().getRaceLogTrackingInfo(object.getB()).raceLogTrackingState;
if (trackingState == RaceLogTrackingState.AWAITING_RACE_DEFINITION || (trackingState == RaceLogTrackingState.TRACKING && !trackerExists)) {
result.add(new ImageSpec(ACTION_START_TRACKING, stringMessages.startTracking(), makeImagePrototype(resources.startRaceLogTracking())));
} else {
if (startEndTrackingTime.getB() == null || startEndTrackingTime.getB().getTimePoint() == null) {
result.add(new ImageSpec(ACTION_STOP_TRACKING, stringMessages.stopTracking(), makeImagePrototype(resources.stopRaceLogTracking())));
}
} else if (trackingState == RaceLogTrackingState.TRACKING && trackerExists) {
result.add(new ImageSpec(ACTION_STOP_TRACKING, stringMessages.stopTracking(), makeImagePrototype(resources.stopRaceLogTracking())));
}
if (smartphoneTrackingEventManagementPanel.getSelectedLeaderboard().canBoatsOfCompetitorsChangePerRace) {
result.add(new ImageSpec(ACTION_EDIT_COMPETITOR_TO_BOAT_MAPPINGS, stringMessages.actionShowCompetitorToBoatAssignments(), makeImagePrototype(resources.sailboatIcon())));
@@ -38,7 +38,6 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog;
import com.sap.sailing.domain.common.dto.BoatDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
@@ -291,19 +290,6 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
return race.getA().getRaceLogTrackingInfo(race.getB()).raceLogTrackingState;
}
private boolean trackerExists(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) {
return race.getA().getRaceLogTrackingInfo(race.getB()).raceLogTrackerExists;
}
private boolean isFinished(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) {
RaceDTO raceDTO = race.getA().getRace(race.getB());
boolean raceFinished = false;
if (raceDTO != null) {
raceFinished = raceDTO.status.status.equals(TrackedRaceStatusEnum.FINISHED);
}
return raceFinished;
}
private boolean doesTrackerExist(
RaceColumnDTOAndFleetDTOWithNameBasedEquality race) {
return race.getA().getRaceLogTrackingInfo(race.getB()).raceLogTrackerExists;
@@ -560,12 +546,13 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
startStopTrackingButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (startStopTrackingButton.isDown()){
startTracking(raceColumnTableSelectionModel.getSelectedSet(), trackWind.getValue(), correctWindDirectionForDeclination.getValue());
} else {
final boolean anyTrackerExists = raceColumnTableSelectionModel.getSelectedSet().stream()
.anyMatch(race -> doesTrackerExist(race));
if (anyTrackerExists) {
stopTracking(raceColumnTableSelectionModel.getSelectedSet());
} else {
startTracking(raceColumnTableSelectionModel.getSelectedSet(), trackWind.getValue(), correctWindDirectionForDeclination.getValue());
}
refreshTrackingActionButtons();
}
});
raceColumnTableSelectionModel.addSelectionChangeHandler(new Handler() {
@@ -578,12 +565,14 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
private void stopTracking(
final Set<RaceColumnDTOAndFleetDTOWithNameBasedEquality> selectedSet) {
final List<RegattaAndRaceIdentifier> racesToStopTracking = new ArrayList<RegattaAndRaceIdentifier>();
final List<RegattaAndRaceIdentifier> racesToStopTracking = new ArrayList<RegattaAndRaceIdentifier>();
for (RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnDTOAndFleetDTOWithNameBasedEquality : selectedSet) {
RaceDTO race = raceColumnDTOAndFleetDTOWithNameBasedEquality.getA().getRace(raceColumnDTOAndFleetDTOWithNameBasedEquality.getB());
if (race != null && race.isTracked){
racesToStopTracking.add(race.getRaceIdentifier());
}
if (doesTrackerExist(raceColumnDTOAndFleetDTOWithNameBasedEquality)) {
final RaceDTO race = raceColumnDTOAndFleetDTOWithNameBasedEquality.getA().getRace(raceColumnDTOAndFleetDTOWithNameBasedEquality.getB());
if (race != null) {
racesToStopTracking.add(race.getRaceIdentifier());
}
}
}
sailingServiceWrite.stopTrackingRaces(racesToStopTracking, new MarkedAsyncCallback<Void>(
new AsyncCallback<Void>() {
@@ -604,40 +593,34 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
}
private void enableStartTrackingButtonIfAppropriateRacesSelected() {
boolean onlyUntrackedRacesPresent = raceColumnTableSelectionModel.getSelectedSet().size() > 0;
boolean onlyTrackedRacesPresent = raceColumnTableSelectionModel.getSelectedSet().size() > 0;
boolean onlyRacesWithNonExistentTracker = raceColumnTableSelectionModel.getSelectedSet().size() > 0;
for (RaceColumnDTOAndFleetDTOWithNameBasedEquality race : raceColumnTableSelectionModel.getSelectedSet()) {
if (getTrackingState(race).isForTracking() && isFinished(race)) {
onlyUntrackedRacesPresent = false;
onlyTrackedRacesPresent = false;
}
if (!getTrackingState(race).isForTracking() || doesTrackerExist(race) || isFinished(race)) {
onlyUntrackedRacesPresent = false;
final Set<RaceColumnDTOAndFleetDTOWithNameBasedEquality> selected = raceColumnTableSelectionModel.getSelectedSet();
boolean allCanStart = selected.size() > 0;
boolean allCanStop = selected.size() > 0;
for (final RaceColumnDTOAndFleetDTOWithNameBasedEquality race : selected) {
if (doesTrackerExist(race)) {
allCanStart = false;
} else if (!getTrackingState(race).isForTracking()) {
allCanStart = false;
allCanStop = false;
} else {
onlyTrackedRacesPresent = false;
}
if (trackerExists(race)) {
onlyRacesWithNonExistentTracker = false;
allCanStop = false;
}
}
boolean hasPermissionToChange = leaderboardSelectionModel.getSelectedSet().stream()
final boolean hasPermissionToChange = leaderboardSelectionModel.getSelectedSet().stream()
.filter(new Predicate<StrippedLeaderboardDTO>() {
@Override
public boolean test(StrippedLeaderboardDTO t) {
return userService.hasPermission(t, DefaultActions.UPDATE);
}
}).count() > 0;
if ((!onlyTrackedRacesPresent && !onlyUntrackedRacesPresent)) {
startStopTrackingButton.setEnabled(false);
}
if (onlyTrackedRacesPresent) {
startStopTrackingButton.setDown(true);
startStopTrackingButton.setEnabled(hasPermissionToChange);
}
if (onlyUntrackedRacesPresent || onlyRacesWithNonExistentTracker) {
if (allCanStart) {
startStopTrackingButton.setDown(false);
startStopTrackingButton.setEnabled(hasPermissionToChange);
} else if (allCanStop) {
startStopTrackingButton.setDown(true);
startStopTrackingButton.setEnabled(hasPermissionToChange);
} else {
startStopTrackingButton.setEnabled(false);
}
}
@@ -663,6 +646,12 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
raceColumnsAndFleets.add(new Triple<String, String, String>(selectedLeaderboard.getName(), raceColumn.getName(), fleet.getName()));
}
}
raceColumnTable.getDataProvider().getList().clear();
for (RaceColumnDTO raceColumn : selectedLeaderboard.getRaceList()) {
for (FleetDTO fleet : raceColumn.getFleets()) {
raceColumnTable.getDataProvider().getList().add(new RaceColumnDTOAndFleetDTOWithNameBasedEquality(raceColumn, fleet, selectedLeaderboard));
}
}
sailingServiceWrite.getTrackingTimes(raceColumnsAndFleets,
new AsyncCallback<Map<Triple<String, String, String>, Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog>>>() {
@Override
@@ -673,13 +662,6 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
@Override
public void onSuccess(Map<Triple<String, String, String>, Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog>> result) {
raceWithStartAndEndOfTrackingTime = result;
raceColumnTable.getDataProvider().getList().clear();
for (RaceColumnDTO raceColumn : selectedLeaderboard.getRaceList()) {
for (FleetDTO fleet : raceColumn.getFleets()) {
RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnDTOAndFleet2 = new RaceColumnDTOAndFleetDTOWithNameBasedEquality(raceColumn, fleet, getSelectedLeaderboard());
raceColumnTable.getDataProvider().getList().add(raceColumnDTOAndFleet2);
}
}
}
});
selectedLeaderBoardPanel.setVisible(true);
@@ -132,4 +132,24 @@ public class CourseTemplateDTO extends NamedDTO implements SecuredDTO {
public Integer getDefaultNumberOfLaps() {
return defaultNumberOfLaps;
}
@Override
public int hashCode() {
return uuid != null ? uuid.hashCode() : System.identityHashCode(this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CourseTemplateDTO)) {
return false;
}
final CourseTemplateDTO other = (CourseTemplateDTO) obj;
if (uuid == null || other.uuid == null) {
return false;
}
return uuid.equals(other.uuid);
}
}
@@ -94,4 +94,24 @@ public class MarkPropertiesDTO extends NamedDTO implements SecuredDTO {
public String getPositioningType() {
return positioningType;
}
@Override
public int hashCode() {
return uuid != null ? uuid.hashCode() : System.identityHashCode(this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MarkPropertiesDTO)) {
return false;
}
final MarkPropertiesDTO other = (MarkPropertiesDTO) obj;
if (uuid == null || other.uuid == null) {
return false;
}
return uuid.equals(other.uuid);
}
}
@@ -76,4 +76,24 @@ public class MarkRoleDTO extends NamedDTO implements SecuredDTO {
securityInformation.setOwnership(createOwnershipDTO);
}
@Override
public int hashCode() {
return uuid != null ? uuid.hashCode() : System.identityHashCode(this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MarkRoleDTO)) {
return false;
}
final MarkRoleDTO other = (MarkRoleDTO) obj;
if (uuid == null || other.uuid == null) {
return false;
}
return uuid.equals(other.uuid);
}
}
@@ -80,4 +80,24 @@ public class MarkTemplateDTO extends NamedDTO implements SecuredDTO {
return commonMarkProperties;
}
@Override
public int hashCode() {
return uuid != null ? uuid.hashCode() : System.identityHashCode(this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MarkTemplateDTO)) {
return false;
}
final MarkTemplateDTO other = (MarkTemplateDTO) obj;
if (uuid == null || other.uuid == null) {
return false;
}
return uuid.equals(other.uuid);
}
}
@@ -23,6 +23,13 @@
<div class="mainContent">
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
<div class="innerContent">
<h2 class="articleSubheadline">May 2026</h2>
<ul class="bulletList">
<li>Course templates, mark templates, mark roles, and mark properties are now correctly
identified by their unique ID rather than their name. This fixes an issue where two
entries sharing the same name were incorrectly treated as equal, and ensures stable
ordering and deduplication in course creation.</li>
</ul>
<h2 class="articleSubheadline">April 2026</h2>
<ul class="bulletList">
<li>Tables with multi-selection support now have a select/de-select all checkbox in the table header.