mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-16 02:38:44 +00:00
Merge branch 'master' into bug4454
Conflicts: translation_v2.json
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
public final class ExpeditionAllInOneConstants {
|
||||
public static final String REQUEST_PARAMETER_IMPORT_MODE = "importMode";
|
||||
public static final String REQUEST_PARAMETER_BOAT_CLASS = "boatClass";
|
||||
public static final String REQUEST_PARAMETER_REGATTA_NAME = "regattaName";
|
||||
public static final String REQUEST_PARAMETER_LOCALE = "locale";
|
||||
public enum ImportMode{
|
||||
NEW_COMPETITOR, NEW_EVENT, NEW_RACE;
|
||||
}
|
||||
|
||||
private ExpeditionAllInOneConstants() {
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.sap.sailing.gwt.common.client.suggestion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.util.NaturalComparator;
|
||||
|
||||
public class RegattaSuggestOracle extends AbstractListSuggestOracle<RegattaDTO> implements RegattasDisplayer {
|
||||
public RegattaSuggestOracle() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createSuggestionKeyString(RegattaDTO value) {
|
||||
return value.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createSuggestionAdditionalDisplayString(RegattaDTO value) {
|
||||
return value.boatClass.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<String> getMatchingStrings(RegattaDTO value) {
|
||||
return Arrays.asList(value.getName(), value.boatClass.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillRegattas(Iterable<RegattaDTO> result) {
|
||||
final List<RegattaDTO> sorted = new ArrayList<>();
|
||||
final NaturalComparator nameComparator = new NaturalComparator(/* case sensitive */ false);
|
||||
Util.addAll(result, sorted);
|
||||
Collections.sort(sorted, (r1, r2)->nameComparator.compare(r1.getName(), r2.getName()));
|
||||
setSelectableValues(sorted);
|
||||
}
|
||||
}
|
||||
+6
@@ -146,6 +146,7 @@ public class CompetitorRegistrationsPanel extends FlowPanel implements BusyDispl
|
||||
true, /* enablePager */true, /* filterCompetitorWithBoat */ canBoatsOfCompetitorsChangePerRace, /* filterCompetitorsWithoutBoat */ !canBoatsOfCompetitorsChangePerRace);
|
||||
registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, /* multiSelection */
|
||||
true, /* enablePager */false, /* filterCompetitorWithBoat */ false, /* filterCompetitorsWithoutBoat */ false);
|
||||
registeredCompetitorsTable.getSelectionModel().addSelectionChangeHandler(event -> validateAndUpdate());
|
||||
allCompetitorsPanel.add(allCompetitorsTable);
|
||||
registeredCompetitorsPanel.add(registeredCompetitorsTable);
|
||||
VerticalPanel movePanel = new VerticalPanel();
|
||||
@@ -229,6 +230,7 @@ public class CompetitorRegistrationsPanel extends FlowPanel implements BusyDispl
|
||||
protected void addImportedCompetitorsToRegisteredCompetitorsTableAndRemoveFromAllCompetitorsTable(Iterable<CompetitorWithBoatDTO> competitorsImported) {
|
||||
allCompetitorsTable.getFilterField().removeAll(competitorsImported);
|
||||
registeredCompetitorsTable.getFilterField().addAll(competitorsImported);
|
||||
validateAndUpdate();
|
||||
}
|
||||
|
||||
private void moveSelected(CompetitorTableWrapper<RefreshableMultiSelectionModel<CompetitorWithBoatDTO>> from,
|
||||
@@ -319,4 +321,8 @@ public class CompetitorRegistrationsPanel extends FlowPanel implements BusyDispl
|
||||
public void moveFromPoolToRegistered(Collection<CompetitorWithBoatDTO> registeredCompetitors) {
|
||||
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
|
||||
}
|
||||
|
||||
public Set<CompetitorWithBoatDTO> getSelectedRegisteredCompetitors() {
|
||||
return registeredCompetitorsTable.getSelectionModel().getSelectedSet();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox<EventDT
|
||||
// remark: startDate == null and endDate == null is valid
|
||||
if (startDate != null && endDate != null) {
|
||||
if (startDate.after(endDate)) {
|
||||
datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate();
|
||||
datesErrorMessage = stringMessages.startDateMustBeforeEndDate();
|
||||
}
|
||||
} else if ((startDate != null && endDate == null) || (startDate == null && endDate != null)) {
|
||||
datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate();
|
||||
|
||||
Regular → Executable
+99
-36
@@ -20,39 +20,49 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.TypedDeviceMappingDTO;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
|
||||
|
||||
/**
|
||||
* This is the UI wizard to be executed after
|
||||
* {@link com.sap.sailing.server.gateway.trackfiles.impl.ExpeditionAllInOneImporter ExpeditionAllInOneImporter}
|
||||
* succeeded running. This provides the functionality to add new {@link com.sap.sailing.domain.base.Competitor
|
||||
* Competitors} to the {@link com.sap.sailing.domain.base.Regatta Regatta} and automatically maps the imported GPS and
|
||||
* Bravo tracks to a selected {@link com.sap.sailing.domain.base.Competitor Competitor}. In addition, the
|
||||
* {@link com.sap.sailing.domain.tracking.TrackedRace TrackedRace} is retracked after the process to ensure that newly
|
||||
* added competitors are correctly visible afterwards. After the import finished successfully, a dialog is provided to
|
||||
* the user that shows links to the event page and race board.
|
||||
*/
|
||||
public class ExpeditionAllInOneAfterImportHandler {
|
||||
|
||||
private final SailingServiceAsync sailingService;
|
||||
private final ErrorReporter errorReporter;
|
||||
private final StringMessages stringMessages;
|
||||
private final RegattaNameAndRaceName regattaAndRaceIdentifier;
|
||||
private final String leaderboardGroupName;
|
||||
private final String raceColumnName;
|
||||
private final String fleetName;
|
||||
protected EventDTO event;
|
||||
private RegattaDTO regatta;
|
||||
private StrippedLeaderboardDTO leaderboard;
|
||||
private List<TrackFileImportDeviceIdentifierDTO> gpsFixesDeviceIDs;
|
||||
private List<TrackFileImportDeviceIdentifierDTO> sensorFixesDeviceIDs;
|
||||
private final String sensorImporterType;
|
||||
private List<Triple<String, String, String>> raceEntries;
|
||||
private String regattaName;
|
||||
|
||||
public ExpeditionAllInOneAfterImportHandler(UUID eventId, String regattaName, String leaderboardName,
|
||||
String leaderboardGroupName, String raceName, String raceColumnName, String fleetName,
|
||||
String leaderboardGroupName, List<Triple<String, String, String>> raceEntries,
|
||||
List<String> gpsDeviceIds, List<String> sensorDeviceIds, String sensorImporterType,
|
||||
final SailingServiceAsync sailingService, final ErrorReporter errorReporter,
|
||||
final StringMessages stringMessages) {
|
||||
this.leaderboardGroupName = leaderboardGroupName;
|
||||
this.raceColumnName = raceColumnName;
|
||||
this.fleetName = fleetName;
|
||||
this.sensorImporterType = sensorImporterType;
|
||||
this.sailingService = sailingService;
|
||||
this.errorReporter = errorReporter;
|
||||
this.stringMessages = stringMessages;
|
||||
this.regattaAndRaceIdentifier = new RegattaNameAndRaceName(regattaName, raceName);
|
||||
this.raceEntries = raceEntries;
|
||||
this.regattaName = regattaName;
|
||||
|
||||
sailingService.getEventById(eventId, false, new DataLoadingCallback<EventDTO>() {
|
||||
@Override
|
||||
@@ -89,32 +99,77 @@ public class ExpeditionAllInOneAfterImportHandler {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class RegattaLogCompetitorRegistrationAndSelectionDialog extends RegattaLogCompetitorRegistrationDialog {
|
||||
public RegattaLogCompetitorRegistrationAndSelectionDialog(String boatClass, SailingServiceAsync sailingService,
|
||||
StringMessages stringMessages, ErrorReporter errorReporter, boolean editable, String leaderboardName,
|
||||
boolean canBoatsOfCompetitorsChangePerRace) {
|
||||
this(boatClass, sailingService, stringMessages, errorReporter, editable, leaderboardName,
|
||||
canBoatsOfCompetitorsChangePerRace, new ValidatorForCompetitorRegistrationDialog(stringMessages),
|
||||
new CallbackForCompetitorRegistrationDialog());
|
||||
}
|
||||
|
||||
public RegattaLogCompetitorRegistrationAndSelectionDialog(String boatClass, SailingServiceAsync sailingService,
|
||||
StringMessages stringMessages, ErrorReporter errorReporter, boolean editable, String leaderboardName,
|
||||
boolean canBoatsOfCompetitorsChangePerRace, ValidatorForCompetitorRegistrationDialog validator,
|
||||
CallbackForCompetitorRegistrationDialog callback) {
|
||||
super(boatClass, sailingService, stringMessages, errorReporter, editable, leaderboardName,
|
||||
canBoatsOfCompetitorsChangePerRace, validator, callback);
|
||||
validator.setCompetitorRegistrationsPanel(competitorRegistrationsPanel);
|
||||
callback.setCompetitorRegistrationsPanel(competitorRegistrationsPanel);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ValidatorForCompetitorRegistrationDialog implements Validator<Set<CompetitorWithBoatDTO>> {
|
||||
private CompetitorRegistrationsPanel competitorRegistrationsPanel;
|
||||
private StringMessages stringMessages;
|
||||
public ValidatorForCompetitorRegistrationDialog(StringMessages stringMessages) {
|
||||
this.stringMessages = stringMessages;
|
||||
}
|
||||
@Override
|
||||
public String getErrorMessage(Set<CompetitorWithBoatDTO> valueToValidate) {
|
||||
if (competitorRegistrationsPanel == null || competitorRegistrationsPanel.getSelectedRegisteredCompetitors().size() != 1) {
|
||||
return stringMessages.selectOneCompetitorToMapTheImportedData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void setCompetitorRegistrationsPanel(CompetitorRegistrationsPanel competitorRegistrationsPanel) {
|
||||
this.competitorRegistrationsPanel = competitorRegistrationsPanel;
|
||||
}
|
||||
}
|
||||
|
||||
private class CallbackForCompetitorRegistrationDialog extends CancelImportDialogCallback<Set<CompetitorWithBoatDTO>> {
|
||||
private CompetitorRegistrationsPanel competitorRegistrationsPanel;
|
||||
|
||||
@Override
|
||||
public void ok(Set<CompetitorWithBoatDTO> competitors) {
|
||||
if (competitors.isEmpty()) {
|
||||
Window.alert(stringMessages.importCanceledNoCompetitorAdded());
|
||||
} else {
|
||||
sailingService.setCompetitorRegistrationsInRegattaLog(leaderboard.getName(),
|
||||
competitors, new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
mapCompetitorsToGPSFixDeviceIds(competitorRegistrationsPanel == null ? competitors
|
||||
: competitorRegistrationsPanel.getSelectedRegisteredCompetitors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError("Failed to register competitors!");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
public void setCompetitorRegistrationsPanel(CompetitorRegistrationsPanel competitorRegistrationsPanel) {
|
||||
this.competitorRegistrationsPanel = competitorRegistrationsPanel;
|
||||
}
|
||||
}
|
||||
|
||||
private void showCompetitorRegistration() {
|
||||
new RegattaLogCompetitorRegistrationDialog(regatta.boatClass == null ? null : regatta.boatClass.getName(),
|
||||
new RegattaLogCompetitorRegistrationAndSelectionDialog(regatta.boatClass == null ? null : regatta.boatClass.getName(),
|
||||
sailingService, stringMessages, errorReporter, true, leaderboard.getName(),
|
||||
leaderboard.canBoatsOfCompetitorsChangePerRace,
|
||||
new CancelImportDialogCallback<Set<CompetitorWithBoatDTO>>() {
|
||||
@Override
|
||||
public void ok(final Set<CompetitorWithBoatDTO> competitors) {
|
||||
if (competitors.isEmpty()) {
|
||||
Window.alert(stringMessages.importCanceledNoCompetitorAdded());
|
||||
} else {
|
||||
sailingService.setCompetitorRegistrationsInRegattaLog(leaderboard.getName(),
|
||||
competitors, new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
mapCompetitorsToGPSFixDeviceIds(competitors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError("Failed to register competitors!");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
leaderboard.canBoatsOfCompetitorsChangePerRace).show();
|
||||
}
|
||||
|
||||
private void mapCompetitorsToGPSFixDeviceIds(final Set<CompetitorWithBoatDTO> mappedCompetitors) {
|
||||
@@ -172,7 +227,17 @@ public class ExpeditionAllInOneAfterImportHandler {
|
||||
|
||||
private final void continueWithMappedDevices() {
|
||||
List<RegattaNameAndRaceName> racesToStopAndStartTrackingFor = new ArrayList<>();
|
||||
racesToStopAndStartTrackingFor.add(regattaAndRaceIdentifier);
|
||||
final List<Triple<String, String, String>> leaderboardRaceColumnFleetNames = new ArrayList<>();
|
||||
final List<Pair<String, String>> raceNames = new ArrayList<>();
|
||||
for(Triple<String, String, String> race:raceEntries) {
|
||||
String raceName = race.getA();
|
||||
String raceColumnName = race.getB();
|
||||
String fleetName= race.getC();
|
||||
racesToStopAndStartTrackingFor.add(new RegattaNameAndRaceName(regattaName, raceName));
|
||||
leaderboardRaceColumnFleetNames.add(new Triple<>(leaderboard.name, raceColumnName, fleetName));
|
||||
raceNames.add(new Pair<String, String>(raceName, raceColumnName));
|
||||
}
|
||||
|
||||
sailingService.removeAndUntrackRaces(racesToStopAndStartTrackingFor, new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
@@ -181,14 +246,12 @@ public class ExpeditionAllInOneAfterImportHandler {
|
||||
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
final List<Triple<String, String, String>> leaderboardRaceColumnFleetNames = new ArrayList<>();
|
||||
leaderboardRaceColumnFleetNames.add(new Triple<>(leaderboard.name, raceColumnName, fleetName));
|
||||
sailingService.startRaceLogTracking(leaderboardRaceColumnFleetNames, /* trackWind */ true,
|
||||
/* TODO correctWindByDeclination */ true, new AsyncCallback<Void>() {
|
||||
sailingService.startRaceLogTracking(leaderboardRaceColumnFleetNames, /* trackWind */ false,
|
||||
/* correctWindByDeclination */ true, new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
new ExpeditionAllInOneImportResultDialog(event.id, regatta.getName(),
|
||||
regattaAndRaceIdentifier.getRaceName(), leaderboard.getName(),
|
||||
raceNames, leaderboard.getName(),
|
||||
leaderboardGroupName).show();
|
||||
}
|
||||
|
||||
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package com.sap.sailing.gwt.ui.adminconsole;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.event.dom.client.ChangeEvent;
|
||||
import com.google.gwt.event.dom.client.ChangeHandler;
|
||||
import com.google.gwt.event.logical.shared.SelectionEvent;
|
||||
import com.google.gwt.event.logical.shared.SelectionHandler;
|
||||
import com.google.gwt.i18n.client.LocaleInfo;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.Composite;
|
||||
import com.google.gwt.user.client.ui.FileUpload;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
import com.google.gwt.user.client.ui.FormPanel;
|
||||
import com.google.gwt.user.client.ui.HasVerticalAlignment;
|
||||
import com.google.gwt.user.client.ui.Hidden;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.RadioButton;
|
||||
import com.google.gwt.user.client.ui.SuggestBox;
|
||||
import com.google.gwt.user.client.ui.SuggestOracle;
|
||||
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.sap.sailing.domain.common.dto.ExpeditionAllInOneConstants;
|
||||
import com.sap.sailing.gwt.common.client.suggestion.BoatClassMasterdataSuggestOracle;
|
||||
import com.sap.sailing.gwt.common.client.suggestion.RegattaSuggestOracle;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.resulthandling.ExpeditionDataImportResponse;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.resulthandling.ExpeditionDataImportResultsDialog;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
|
||||
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
|
||||
|
||||
/**
|
||||
* The UI form to upload data for expedition all in one import.
|
||||
*/
|
||||
public class ExpeditionAllInOneImportPanel extends Composite implements RegattasDisplayer {
|
||||
private static final String URL_SAILINGSERVER_EXPEDITION_FULL_IMPORT = "/../../sailingserver/expedition/import";
|
||||
|
||||
private final RegattaSuggestOracle regattaOracle;
|
||||
|
||||
public ExpeditionAllInOneImportPanel(final StringMessages stringMessages, final SailingServiceAsync sailingService,
|
||||
final ErrorReporter errorReporter, final RegattaRefresher regattaRefresher) {
|
||||
final FormPanel formPanel = new FormPanel();
|
||||
final BusyIndicator busyIndicator = new SimpleBusyIndicator();
|
||||
final Button uploadButton = new Button(stringMessages.upload());
|
||||
formPanel.getElement().setAttribute("autocomplete", "off");
|
||||
uploadButton.addClickHandler(event -> {
|
||||
uploadButton.setEnabled(false);
|
||||
busyIndicator.setBusy(true);
|
||||
formPanel.submit();
|
||||
});
|
||||
formPanel.setMethod(FormPanel.METHOD_POST);
|
||||
formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
|
||||
formPanel.setAction(GWT.getHostPageBaseURL() + URL_SAILINGSERVER_EXPEDITION_FULL_IMPORT);
|
||||
final VerticalPanel contentPanel = new VerticalPanel();
|
||||
formPanel.add(contentPanel);
|
||||
final Hidden localeField = new Hidden(ExpeditionAllInOneConstants.REQUEST_PARAMETER_LOCALE, LocaleInfo.getCurrentLocale().getLocaleName());
|
||||
contentPanel.add(localeField);
|
||||
final FileUpload fileUpload = new FileUpload();
|
||||
fileUpload.setName("upload");
|
||||
contentPanel.add(fileUpload);
|
||||
|
||||
final FlowPanel importModePanel = new FlowPanel();
|
||||
contentPanel.add(importModePanel);
|
||||
final HorizontalPanel regattaNamePanel = new HorizontalPanel();
|
||||
regattaNamePanel.setVisible(false);
|
||||
final HorizontalPanel boatClassPanel = new HorizontalPanel();
|
||||
contentPanel.add(regattaNamePanel);
|
||||
final RadioButton newEventImport = new RadioButton(ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_MODE,
|
||||
stringMessages.createNewEvent());
|
||||
newEventImport.setFormValue(ExpeditionAllInOneConstants.ImportMode.NEW_EVENT.name());
|
||||
newEventImport.setValue(true);
|
||||
importModePanel.add(newEventImport);
|
||||
newEventImport.addClickHandler(event -> {
|
||||
regattaNamePanel.setVisible(false);
|
||||
boatClassPanel.setVisible(true);
|
||||
});
|
||||
final RadioButton newCompetitorImport = new RadioButton(
|
||||
ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_MODE, stringMessages.newExpeditionCompetitor());
|
||||
newCompetitorImport.setFormValue(ExpeditionAllInOneConstants.ImportMode.NEW_COMPETITOR.name());
|
||||
importModePanel.add(newCompetitorImport);
|
||||
newCompetitorImport.addClickHandler(event -> {
|
||||
regattaNamePanel.setVisible(true);
|
||||
boatClassPanel.setVisible(false);
|
||||
});
|
||||
final RadioButton newRaceImport = new RadioButton(ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_MODE,
|
||||
stringMessages.newExpeditionRace());
|
||||
newRaceImport.setFormValue(ExpeditionAllInOneConstants.ImportMode.NEW_RACE.name());
|
||||
importModePanel.add(newRaceImport);
|
||||
newRaceImport.addClickHandler(event -> {
|
||||
regattaNamePanel.setVisible(true);
|
||||
boatClassPanel.setVisible(false);
|
||||
});
|
||||
|
||||
regattaNamePanel.setSpacing(5);
|
||||
final Label regattaNameLabel = new Label(stringMessages.regattaName() + ":");
|
||||
regattaNamePanel.add(regattaNameLabel);
|
||||
regattaNamePanel.setCellVerticalAlignment(regattaNameLabel, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
final TextBox regattaName = new TextBox();
|
||||
regattaName.setName(ExpeditionAllInOneConstants.REQUEST_PARAMETER_REGATTA_NAME);
|
||||
|
||||
regattaOracle = new RegattaSuggestOracle();
|
||||
final SuggestBox regattaSuggestBox = new SuggestBox(regattaOracle, regattaName);
|
||||
regattaSuggestBox.getValueBox().getElement().getStyle().setProperty("minWidth", 30, Unit.EM);
|
||||
regattaSuggestBox.getValueBox().getElement().setAttribute("placeholder", stringMessages.startTypingForSuggestions());
|
||||
regattaNamePanel.add(regattaSuggestBox);
|
||||
regattaNamePanel.setCellVerticalAlignment(regattaSuggestBox, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
boatClassPanel.setSpacing(5);
|
||||
contentPanel.add(boatClassPanel);
|
||||
final Label boatClassLabel = new Label(stringMessages.boatClass() + ":");
|
||||
boatClassPanel.add(boatClassLabel);
|
||||
boatClassPanel.setCellVerticalAlignment(boatClassLabel, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
final SuggestBox boatClassInput = new SuggestBox(new BoatClassMasterdataSuggestOracle());
|
||||
boatClassInput.getValueBox().getElement().getStyle().setProperty("minWidth", 30, Unit.EM);
|
||||
boatClassInput.getValueBox().getElement().setAttribute("placeholder", stringMessages.startTypingForSuggestions());
|
||||
boatClassInput.getValueBox().setName(ExpeditionAllInOneConstants.REQUEST_PARAMETER_BOAT_CLASS);
|
||||
boatClassPanel.add(boatClassInput);
|
||||
boatClassPanel.setCellVerticalAlignment(boatClassInput, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
final HorizontalPanel controlPanel = new HorizontalPanel();
|
||||
controlPanel.setSpacing(5);
|
||||
controlPanel.add(uploadButton);
|
||||
controlPanel.add(busyIndicator);
|
||||
contentPanel.add(controlPanel);
|
||||
final Runnable validation = () -> {
|
||||
final String filename = fileUpload.getFilename(), boatClass = boatClassInput.getValue();
|
||||
final String regattaNameValue = regattaSuggestBox.getValue();
|
||||
final boolean fileValid = filename != null && !filename.trim().isEmpty();
|
||||
final boolean isNewEventImport = Boolean.TRUE.equals(newEventImport.getValue());
|
||||
final boolean boatClassValid = boatClass != null && !boatClass.trim().isEmpty();
|
||||
final boolean regattaNameValid = regattaNameValue != null && !regattaNameValue.trim().isEmpty();
|
||||
uploadButton.setEnabled(fileValid && (isNewEventImport ? boatClassValid : regattaNameValid));
|
||||
};
|
||||
fileUpload.addChangeHandler(new ChangeHandler() {
|
||||
@Override
|
||||
public void onChange(ChangeEvent event) {
|
||||
validation.run();
|
||||
}
|
||||
});
|
||||
formPanel.addSubmitCompleteHandler(event -> {
|
||||
validation.run();
|
||||
busyIndicator.setBusy(false);
|
||||
final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(event.getResults());
|
||||
if (response == null) {
|
||||
Window.alert(StringMessages.INSTANCE.unexpectedErrorDuringFileImport());
|
||||
} else if (response.hasEventId()) {
|
||||
|
||||
new ExpeditionAllInOneAfterImportHandler(response.getEventId(), response.getRegattaName(),
|
||||
response.getLeaderboardName(), response.getLeaderboardGroupName(), response.getRaceEntries(), response.getGpsDeviceIds(),
|
||||
response.getSensorDeviceIds(), response.getSensorFixImporterType(), sailingService,
|
||||
errorReporter, stringMessages);
|
||||
regattaRefresher.fillRegattas();
|
||||
} else {
|
||||
ExpeditionDataImportResultsDialog.showResults(response);
|
||||
}
|
||||
});
|
||||
regattaSuggestBox.addSelectionHandler(new SelectionHandler<SuggestOracle.Suggestion>() {
|
||||
@Override
|
||||
public void onSelection(SelectionEvent<Suggestion> event) {
|
||||
final String selected = event.getSelectedItem().getReplacementString();
|
||||
if (selected != null) {
|
||||
sailingService.getRegattaByName(selected, new AsyncCallback<RegattaDTO>() {
|
||||
@Override
|
||||
public void onSuccess(RegattaDTO result) {
|
||||
validation.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError("Could not determine Regatta " + selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
regattaSuggestBox.addKeyUpHandler(event -> validation.run());
|
||||
fileUpload.addChangeHandler(event -> validation.run());
|
||||
boatClassInput.addSelectionHandler(event -> validation.run());
|
||||
boatClassInput.addKeyUpHandler(event -> validation.run());
|
||||
validation.run();
|
||||
initWidget(formPanel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillRegattas(Iterable<RegattaDTO> regattas) {
|
||||
regattaOracle.fillRegattas(regattas);
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -1,15 +1,17 @@
|
||||
package com.sap.sailing.gwt.ui.adminconsole;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.shared.race.TrackedRaceCreationResultDialog;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public class ExpeditionAllInOneImportResultDialog extends TrackedRaceCreationResultDialog {
|
||||
|
||||
public ExpeditionAllInOneImportResultDialog(UUID eventId, String regattaName, String raceName,
|
||||
public ExpeditionAllInOneImportResultDialog(UUID eventId, String regattaName, List<Pair<String, String>> raceEntries,
|
||||
String leaderboardName, String leaderboardGroupName) {
|
||||
super(StringMessages.INSTANCE.importFinished(), StringMessages.INSTANCE.importFinishedMessage(), eventId,
|
||||
regattaName, raceName, leaderboardName, leaderboardGroupName);
|
||||
regattaName, raceEntries, leaderboardName, leaderboardGroupName);
|
||||
}
|
||||
}
|
||||
Regular → Executable
+10
-1
@@ -16,7 +16,16 @@ public class RegattaLogCompetitorRegistrationDialog extends AbstractCompetitorRe
|
||||
public RegattaLogCompetitorRegistrationDialog(String boatClass, SailingServiceAsync sailingService,
|
||||
StringMessages stringMessages, ErrorReporter errorReporter, boolean editable, String leaderboardName, boolean canBoatsOfCompetitorsChangePerRace,
|
||||
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<Set<CompetitorWithBoatDTO>> callback) {
|
||||
super(sailingService, stringMessages, errorReporter, editable, callback, leaderboardName, canBoatsOfCompetitorsChangePerRace, boatClass, stringMessages.save(), /* validator */ null);
|
||||
this(boatClass, sailingService, stringMessages, errorReporter, editable, leaderboardName,
|
||||
canBoatsOfCompetitorsChangePerRace, /* validator */ null, callback);
|
||||
}
|
||||
|
||||
public RegattaLogCompetitorRegistrationDialog(String boatClass, SailingServiceAsync sailingService,
|
||||
StringMessages stringMessages, ErrorReporter errorReporter, boolean editable, String leaderboardName,
|
||||
boolean canBoatsOfCompetitorsChangePerRace, Validator<Set<CompetitorWithBoatDTO>> validator,
|
||||
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<Set<CompetitorWithBoatDTO>> callback) {
|
||||
super(sailingService, stringMessages, errorReporter, editable, callback, leaderboardName,
|
||||
canBoatsOfCompetitorsChangePerRace, boatClass, stringMessages.save(), validator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+22
-88
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -44,7 +45,6 @@ import com.google.gwt.user.client.ui.Hidden;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.Panel;
|
||||
import com.google.gwt.user.client.ui.SuggestBox;
|
||||
import com.google.gwt.user.client.ui.TabPanel;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
@@ -55,10 +55,7 @@ import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.gwt.common.client.suggestion.BoatClassMasterdataSuggestOracle;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.WindImportResult.RaceEntry;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.resulthandling.ExpeditionDataImportResponse;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.resulthandling.ExpeditionDataImportResultsDialog;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
@@ -70,12 +67,11 @@ import com.sap.sailing.gwt.ui.shared.WindDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
||||
import com.sap.sse.gwt.client.celltable.BaseCelltable;
|
||||
import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel;
|
||||
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
|
||||
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
|
||||
|
||||
/**
|
||||
@@ -87,13 +83,10 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
|
||||
*
|
||||
*/
|
||||
public class WindPanel extends FormPanel implements RegattasDisplayer, WindShower {
|
||||
|
||||
private static final String WIND_IMPORT_PARAMETER_RACES = "races";
|
||||
|
||||
private static final String EXPEDITON_IMPORT_PARAMETER_BOAT_ID = "boatId";
|
||||
private static final String EXPEDITON_IMPORT_PARAMETER_BOAT_CLASS = "boatClass";
|
||||
|
||||
private static final String URL_SAILINGSERVER_EXPEDITION_FULL_IMPORT = "/../../sailingserver/expedition/import";
|
||||
|
||||
private static final String URL_SAILINGSERVER_EXPEDITION_IMPORT = "/../../sailingserver/expedition-import";
|
||||
private static final String URL_SAILINGSERVER_GRIB_IMPORT = "/../../sailingserver/grib-wind-import";
|
||||
private static final String URL_SAILINGSERVER_NMEA_IMPORT = "/../../sailingserver/nmea-wind-import";
|
||||
@@ -117,23 +110,28 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
private final ListDataProvider<WindDTO> rawWindFixesDataProvider;
|
||||
private final CellTable<WindDTO> rawWindFixesTable;
|
||||
private final VerticalPanel windFixPanel;
|
||||
|
||||
|
||||
/**
|
||||
* Composite pattern over the {@link RegattasDisplayer} interface. Calls to {@link #fillRegattas(Iterable)}
|
||||
* will be forwarded to those objects contained in this collection.
|
||||
*/
|
||||
private final Set<RegattasDisplayer> containedRegattaDisplayers;
|
||||
|
||||
public WindPanel(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
|
||||
ErrorReporter errorReporter, RegattaRefresher regattaRefresher, final StringMessages stringMessages) {
|
||||
ensureDebugId("WindPanel");
|
||||
|
||||
this.sailingService = sailingService;
|
||||
this.containedRegattaDisplayers = new HashSet<>();
|
||||
this.errorReporter = errorReporter;
|
||||
this.stringMessages = stringMessages;
|
||||
windSourcesToExcludeSelectorPanel = new WindSourcesToExcludeSelectorPanel(sailingService, stringMessages, errorReporter);
|
||||
|
||||
VerticalPanel mainPanel = new VerticalPanel();
|
||||
mainPanel.setSize("100%", "100%");
|
||||
this.setWidget(mainPanel);
|
||||
|
||||
trackedRacesListComposite = new TrackedRacesListComposite(null, null, sailingService, errorReporter,
|
||||
regattaRefresher,
|
||||
stringMessages, /*multiselection*/true, /* actionButtonsEnabled */ false);
|
||||
containedRegattaDisplayers.add(trackedRacesListComposite);
|
||||
trackedRacesListComposite.ensureDebugId("TrackedRacesListComposite");
|
||||
mainPanel.add(trackedRacesListComposite);
|
||||
refreshableRaceSelectionModel = (RefreshableMultiSelectionModel<RaceDTO>) trackedRacesListComposite.getSelectionModel();
|
||||
@@ -143,16 +141,13 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
updateWindDisplay();
|
||||
}
|
||||
});
|
||||
|
||||
windCaptionPanel = new CaptionPanel(stringMessages.wind());
|
||||
windCaptionPanel.setVisible(false);
|
||||
mainPanel.add(windCaptionPanel);
|
||||
|
||||
TabPanel tabPanel = new TabPanel();
|
||||
tabPanel.setAnimationEnabled(true);
|
||||
windCaptionPanel.add(tabPanel);
|
||||
tabPanel.setSize("95%", "95%");
|
||||
|
||||
windFixesDisplayPanel = new VerticalPanel();
|
||||
Button addWindFixButton = new Button(stringMessages.actionAddWindData() + "...");
|
||||
addWindFixButton.addClickHandler(new ClickHandler() {
|
||||
@@ -176,16 +171,13 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
}
|
||||
});
|
||||
windFixesDisplayPanel.add(addWindFixButton);
|
||||
|
||||
final VerticalPanel windSourcesPanel = new VerticalPanel();
|
||||
windSourcesPanel.setSpacing(10);
|
||||
tabPanel.add(windSourcesPanel, stringMessages.windSourcesUsed());
|
||||
tabPanel.add(windFixesDisplayPanel, stringMessages.windFixes());
|
||||
tabPanel.selectTab(0);
|
||||
|
||||
raceIsKnownToStartUpwindBox = new CheckBox(stringMessages.raceIsKnownToStartUpwind());
|
||||
windSourcesPanel.add(raceIsKnownToStartUpwindBox);
|
||||
|
||||
windSourcesPanel.add(windSourcesToExcludeSelectorPanel);
|
||||
raceIsKnownToStartUpwindBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
|
||||
@Override
|
||||
@@ -193,13 +185,10 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
setRaceIsKnownToStartUpwind();
|
||||
}
|
||||
});
|
||||
|
||||
windFixPanel = new VerticalPanel();
|
||||
windSourcesPanel.add(windFixPanel);
|
||||
|
||||
windSourceLabel = new Label();
|
||||
windFixesDisplayPanel.add(windSourceLabel);
|
||||
|
||||
// table for the raw wind fixes
|
||||
removeColumn = new IdentityColumn<WindDTO>(new ActionCell<WindDTO>(stringMessages.remove(), new Delegate<WindDTO>() {
|
||||
@Override
|
||||
@@ -252,7 +241,6 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
timeColumn.setSortable(true);
|
||||
speedInKnotsColumn.setSortable(true);
|
||||
windDirectionInDegColumn.setSortable(true);
|
||||
|
||||
AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class);
|
||||
rawWindFixesTable = new BaseCelltable<WindDTO>(/* pageSize */10000, tableRes);
|
||||
rawWindFixesTable.addColumn(timeColumn, stringMessages.time());
|
||||
@@ -266,13 +254,14 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
rawWindFixesTable.addColumnSortHandler(columnSortHandler);
|
||||
rawWindFixesTable.getColumnSortList().push(timeColumn);
|
||||
windFixesDisplayPanel.add(rawWindFixesTable);
|
||||
|
||||
mainPanel.add(createExpeditionWindImportPanel());
|
||||
mainPanel.add(createGribWindImportPanel());
|
||||
mainPanel.add(createNmeaWindImportPanel());
|
||||
mainPanel.add(createBravoWindImportPanel());
|
||||
mainPanel.add(createIgtimiWindImportPanel(mainPanel));
|
||||
mainPanel.add(createExpeditionAllInOneImportPanel());
|
||||
final Pair<CaptionPanel, ExpeditionAllInOneImportPanel> expeditionAllInOneRootAndImportPanel = createExpeditionAllInOneImportPanel(regattaRefresher);
|
||||
mainPanel.add(expeditionAllInOneRootAndImportPanel.getA());
|
||||
containedRegattaDisplayers.add(expeditionAllInOneRootAndImportPanel.getB());
|
||||
}
|
||||
|
||||
private CaptionPanel createIgtimiWindImportPanel(VerticalPanel mainPanel) {
|
||||
@@ -448,68 +437,11 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
return new WindImportFileUploadForm(form, formContentPanel, fileUpload, submitButton);
|
||||
}
|
||||
|
||||
private CaptionPanel createExpeditionAllInOneImportPanel() {
|
||||
private Pair<CaptionPanel, ExpeditionAllInOneImportPanel> createExpeditionAllInOneImportPanel(RegattaRefresher regattaRefresher) {
|
||||
final CaptionPanel rootPanel = new CaptionPanel(stringMessages.importFullExpeditionData());
|
||||
final FormPanel formPanel = new FormPanel();
|
||||
final BusyIndicator busyIndicator = new SimpleBusyIndicator();
|
||||
final Button uploadButton = new Button(stringMessages.upload());
|
||||
uploadButton.addClickHandler(event -> {
|
||||
uploadButton.setEnabled(false);
|
||||
busyIndicator.setBusy(true);
|
||||
formPanel.submit();
|
||||
});
|
||||
formPanel.setMethod(FormPanel.METHOD_POST);
|
||||
formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
|
||||
formPanel.setAction(GWT.getHostPageBaseURL() + URL_SAILINGSERVER_EXPEDITION_FULL_IMPORT);
|
||||
formPanel.addSubmitCompleteHandler(event -> {
|
||||
uploadButton.setEnabled(true);
|
||||
busyIndicator.setBusy(false);
|
||||
final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(event.getResults());
|
||||
if (response == null) {
|
||||
Window.alert(StringMessages.INSTANCE.unexpectedErrorDuringFileImport());
|
||||
} else if (response.hasEventId()) {
|
||||
new ExpeditionAllInOneAfterImportHandler(response.getEventId(), response.getRegattaName(),
|
||||
response.getLeaderboardGroupName(), response.getLeaderboardName(), response.getRaceName(),
|
||||
response.getRaceColumnName(), response.getFleetName(), response.getGpsDeviceIds(),
|
||||
response.getSensorDeviceIds(), response.getSensorFixImporterType(), sailingService,
|
||||
errorReporter, stringMessages);
|
||||
} else {
|
||||
ExpeditionDataImportResultsDialog.showResults(response);
|
||||
}
|
||||
});
|
||||
rootPanel.add(formPanel);
|
||||
final VerticalPanel contentPanel = new VerticalPanel();
|
||||
formPanel.add(contentPanel);
|
||||
final FileUpload fileUpload = new FileUpload();
|
||||
fileUpload.setName("upload");
|
||||
contentPanel.add(fileUpload);
|
||||
final HorizontalPanel boatClassPanel = new HorizontalPanel();
|
||||
boatClassPanel.setSpacing(5);
|
||||
contentPanel.add(boatClassPanel);
|
||||
final Label boatClassLabel = new Label(stringMessages.boatClass() + ":");
|
||||
boatClassPanel.add(boatClassLabel);
|
||||
boatClassPanel.setCellVerticalAlignment(boatClassLabel, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
final SuggestBox boatClassInput = new SuggestBox(new BoatClassMasterdataSuggestOracle());
|
||||
boatClassInput.getValueBox().setName(EXPEDITON_IMPORT_PARAMETER_BOAT_CLASS);
|
||||
boatClassPanel.add(boatClassInput);
|
||||
boatClassPanel.setCellVerticalAlignment(boatClassInput, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
final HorizontalPanel controlPanel = new HorizontalPanel();
|
||||
controlPanel.setSpacing(5);
|
||||
controlPanel.add(uploadButton);
|
||||
controlPanel.add(busyIndicator);
|
||||
contentPanel.add(controlPanel);
|
||||
final Runnable validation = () -> {
|
||||
final String filename = fileUpload.getFilename(), boatClass = boatClassInput.getValue();
|
||||
final boolean fileValid = filename != null && !filename.trim().isEmpty();
|
||||
final boolean boatClassValid = boatClass != null && !boatClass.trim().isEmpty();
|
||||
uploadButton.setEnabled(fileValid && boatClassValid);
|
||||
};
|
||||
|
||||
fileUpload.addChangeHandler(event -> validation.run());
|
||||
boatClassInput.addSelectionHandler(event -> validation.run());
|
||||
boatClassInput.addKeyUpHandler(event -> validation.run());
|
||||
validation.run();
|
||||
return rootPanel;
|
||||
final ExpeditionAllInOneImportPanel expeditionAllInOneImportPanel = new ExpeditionAllInOneImportPanel(stringMessages, sailingService, errorReporter, regattaRefresher);
|
||||
rootPanel.add(expeditionAllInOneImportPanel);
|
||||
return new Pair<>(rootPanel, expeditionAllInOneImportPanel);
|
||||
}
|
||||
|
||||
private CaptionPanel createExpeditionWindImportPanel() {
|
||||
@@ -627,7 +559,9 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
|
||||
@Override
|
||||
public void fillRegattas(Iterable<RegattaDTO> result) {
|
||||
trackedRacesListComposite.fillRegattas(result);
|
||||
for (final RegattasDisplayer containedRegattaDisplayer : containedRegattaDisplayers) {
|
||||
containedRegattaDisplayer.fillRegattas(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+20
-12
@@ -1,8 +1,13 @@
|
||||
package com.sap.sailing.gwt.ui.adminconsole.resulthandling;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gwt.json.client.JSONArray;
|
||||
import com.google.gwt.json.client.JSONObject;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
|
||||
public class ExpeditionDataImportResponse extends AbstractDataImportResponse {
|
||||
|
||||
public static final ExpeditionDataImportResponse parse(String json) {
|
||||
@@ -24,22 +29,25 @@ public class ExpeditionDataImportResponse extends AbstractDataImportResponse {
|
||||
public final native String getLeaderboardName() /*-{
|
||||
return this.leaderboardName;
|
||||
}-*/;
|
||||
|
||||
|
||||
public final native String getRegattaName() /*-{
|
||||
return this.regattaName;
|
||||
}-*/;
|
||||
|
||||
public final native String getRaceName() /*-{
|
||||
return this.raceName;
|
||||
}-*/;
|
||||
|
||||
public final native String getRaceColumnName() /*-{
|
||||
return this.raceColumnName;
|
||||
}-*/;
|
||||
|
||||
public final native String getFleetName() /*-{
|
||||
return this.fleetName;
|
||||
}-*/;
|
||||
public final List<Triple<String, String, String>> getRaceEntries() {
|
||||
ArrayList<Triple<String, String, String>> raceNameRaceColumnNameFleetNameList = new ArrayList<>();
|
||||
JSONObject jsonView = new JSONObject(this);
|
||||
JSONArray entryList = jsonView.get("raceNameRaceColumnNameFleetNameList").isArray();
|
||||
for (int i = 0; i < entryList.size(); i++) {
|
||||
JSONObject entry = entryList.get(i).isObject();
|
||||
String raceName = entry.get("raceName").isString().stringValue();
|
||||
String raceColumnName = entry.get("raceColumnName").isString().stringValue();
|
||||
String fleetName = entry.get("fleetName").isString().stringValue();
|
||||
raceNameRaceColumnNameFleetNameList
|
||||
.add(new Triple<String, String, String>(raceName, raceColumnName, fleetName));
|
||||
}
|
||||
return raceNameRaceColumnNameFleetNameList;
|
||||
}
|
||||
|
||||
public final List<String> getGpsDeviceIds() {
|
||||
return getStringList("gpsDeviceIds");
|
||||
|
||||
Executable → Regular
+5
@@ -2071,6 +2071,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String importFinished();
|
||||
String importFinishedMessage();
|
||||
String importFinishedGotoRaceboard();
|
||||
String importFinishedGotoRaceboardWithName(String name);
|
||||
String importFinishedGotoEvent();
|
||||
String chooseAName();
|
||||
String errorFillingRaceLogsFromPairingList(String message);
|
||||
@@ -2148,4 +2149,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String expeditionRaceJibCarStbd();
|
||||
String expeditionRaceMastButt();
|
||||
String milliBarUnits();
|
||||
String newExpeditionRace();
|
||||
String newExpeditionCompetitor();
|
||||
String selectOneCompetitorToMapTheImportedData();
|
||||
String startTypingForSuggestions();
|
||||
}
|
||||
|
||||
+5
@@ -2066,6 +2066,7 @@ importCanceledByUser=Import canceled!
|
||||
importFinished=Finished importing data
|
||||
importFinishedMessage=Successfully imported expedition data.
|
||||
importFinishedGotoRaceboard=Open raceboard
|
||||
importFinishedGotoRaceboardWithName=Open raceboard for "{0}"
|
||||
importFinishedGotoEvent=Open event
|
||||
chooseAName=Choose a name
|
||||
errorFillingRaceLogsFromPairingList=Error filling race logs from pairing list: {0}
|
||||
@@ -2138,3 +2139,7 @@ expeditionRaceJibCarPort=eaJibCarPFromBow
|
||||
expeditionRaceJibCarStbd=eaJibCarSFromBow
|
||||
expeditionRaceMastButt=exMastButt
|
||||
milliBarUnits=mbar
|
||||
newExpeditionRace=Create a new race
|
||||
newExpeditionCompetitor=Add a new Competitor
|
||||
selectOneCompetitorToMapTheImportedData=Please select one competitor on the left side. The imported data will be mapped to this competitor.
|
||||
startTypingForSuggestions=Start typing to get suggestions
|
||||
|
||||
+5
@@ -2063,6 +2063,7 @@ importCanceledByUser=Import abgebrochen!
|
||||
importFinished=Importvorgang abgeschlossen
|
||||
importFinishedMessage=Erfolgreich Expeditionsdaten eingelesen
|
||||
importFinishedGotoRaceboard=Raceboard öffnen
|
||||
importFinishedGotoRaceboardWithName=Raceboard für "{0}" öffnen
|
||||
importFinishedGotoEvent=Event öffnen
|
||||
chooseAName=Namen auswählen
|
||||
errorFillingRaceLogsFromPairingList=Fehler beim Befüllen der Rennprotokolle aus der Gegner-Paarungsliste: {0}
|
||||
@@ -2134,3 +2135,7 @@ expeditionRaceLoadP=eFShroudP
|
||||
expeditionRaceJibCarPort=eaJibCarPFromBow
|
||||
expeditionRaceJibCarStbd=eaJibCarSFromBow
|
||||
expeditionRaceMastButt=exMastButt
|
||||
newExpeditionRace=Neues Rennen anlegen
|
||||
newExpeditionCompetitor=Neuen Teilnehmer hinzufügen
|
||||
selectOneCompetitorToMapTheImportedData=Bitte einen Teilnehmer auf der linken Seite auswählen. Die importierten Daten werden dem ausgewählten Teinehmer zugeordnet.
|
||||
startTypingForSuggestions=Tippen starten, um Vorschlagsliste zuerhalten
|
||||
|
||||
+32
-14
@@ -1,6 +1,8 @@
|
||||
package com.sap.sailing.gwt.ui.client.shared.race;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gwt.user.client.ui.Anchor;
|
||||
@@ -9,21 +11,32 @@ import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.gwt.settings.client.EntryPointWithSettingsLinkFactory;
|
||||
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
|
||||
|
||||
/**
|
||||
* Summary dialog to be shown after actions that generate a new TrackedRace.
|
||||
* the dialog shows a link to the RaceBoard for the generated TrackedRace.
|
||||
* In addition, if available a link to the associated event is shown.
|
||||
* Summary dialog to be shown after actions that generate a new TrackedRace. the dialog shows a link to the RaceBoard
|
||||
* for the generated TrackedRace. In addition, if available a link to the associated event is shown.
|
||||
*/
|
||||
public class TrackedRaceCreationResultDialog extends DataEntryDialog<Void> {
|
||||
|
||||
private final VerticalPanel verticalPanel;
|
||||
|
||||
public TrackedRaceCreationResultDialog(String title, String message, UUID eventId, String regattaName, String raceName,
|
||||
String leaderboardName, String leaderboardGroupName) {
|
||||
super(title, message,
|
||||
StringMessages.INSTANCE.ok(), StringMessages.INSTANCE.cancel(), /* validator */ null,
|
||||
public TrackedRaceCreationResultDialog(String title, String message, UUID eventId, String regattaName,
|
||||
String raceName, String leaderboardName, String leaderboardGroupName) {
|
||||
this(title, message, eventId, regattaName, createSingleRaceList(raceName), leaderboardName,
|
||||
leaderboardGroupName);
|
||||
}
|
||||
|
||||
private static List<Pair<String, String>> createSingleRaceList(String raceName) {
|
||||
ArrayList<Pair<String, String>> rv = new ArrayList<>();
|
||||
rv.add(new Pair<String, String>(raceName, null));
|
||||
return rv;
|
||||
}
|
||||
|
||||
public TrackedRaceCreationResultDialog(String title, String message, UUID eventId, String regattaName,
|
||||
List<Pair<String, String>> raceNameRaceColumnName, String leaderboardName, String leaderboardGroupName) {
|
||||
super(title, message, StringMessages.INSTANCE.ok(), StringMessages.INSTANCE.cancel(), /* validator */ null,
|
||||
new DialogCallback<Void>() {
|
||||
@Override
|
||||
public void ok(Void editedObject) {
|
||||
@@ -36,19 +49,24 @@ public class TrackedRaceCreationResultDialog extends DataEntryDialog<Void> {
|
||||
|
||||
verticalPanel = new VerticalPanel();
|
||||
verticalPanel.setSpacing(20);
|
||||
Anchor raceboardAnchor = new Anchor(StringMessages.INSTANCE.importFinishedGotoRaceboard(),
|
||||
EntryPointWithSettingsLinkFactory.createRaceBoardLinkWithDefaultSettings(eventId, leaderboardName,
|
||||
leaderboardGroupName, regattaName, raceName));
|
||||
raceboardAnchor.setTarget("_blank");
|
||||
verticalPanel.add(raceboardAnchor);
|
||||
|
||||
boolean multiRace = raceNameRaceColumnName.size() > 1;
|
||||
for (Pair<String, String> raceName : raceNameRaceColumnName) {
|
||||
String anchorLabel = multiRace ? StringMessages.INSTANCE.importFinishedGotoRaceboardWithName(raceName.getB())
|
||||
: StringMessages.INSTANCE.importFinishedGotoRaceboard();
|
||||
Anchor raceboardAnchor = new Anchor(anchorLabel,
|
||||
EntryPointWithSettingsLinkFactory.createRaceBoardLinkWithDefaultSettings(eventId, leaderboardName,
|
||||
leaderboardGroupName, regattaName, raceName.getA()));
|
||||
raceboardAnchor.setTarget("_blank");
|
||||
verticalPanel.add(raceboardAnchor);
|
||||
}
|
||||
|
||||
if (eventId != null) {
|
||||
Anchor eventAnchor = new Anchor(StringMessages.INSTANCE.importFinishedGotoEvent(),
|
||||
EntryPointLinkFactory.createEventPlaceLink(eventId.toString(), Collections.emptyMap()));
|
||||
eventAnchor.setTarget("_blank");
|
||||
verticalPanel.add(eventAnchor);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-5
@@ -10,7 +10,6 @@ import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage;
|
||||
@@ -84,7 +83,6 @@ public class SettingsTest extends AbstractSeleniumTest {
|
||||
* settings and global settings.
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void testRaceBoardPageSettingsStorage() throws InterruptedException, UnsupportedEncodingException {
|
||||
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
|
||||
EventConfigurationPanelPO events = adminConsole.goToEvents();
|
||||
@@ -196,8 +194,8 @@ public class SettingsTest extends AbstractSeleniumTest {
|
||||
Assert.assertTrue(mapSettings.isWindUp());
|
||||
// verify default mode settings override system defaults
|
||||
Assert.assertTrue(mapSettings.isShowOnlySelectedCompetitors());
|
||||
// Verify custom user settings override system defaults
|
||||
Assert.assertTrue(mapSettings.isTransparentHoverlines());
|
||||
// Verify custom user settings are independent for modes
|
||||
Assert.assertFalse(mapSettings.isTransparentHoverlines());
|
||||
mapSettings.setWindUp(false);
|
||||
mapSettings.setTransparentHoverlines(false);
|
||||
mapSettings.pressOk();
|
||||
@@ -219,7 +217,7 @@ public class SettingsTest extends AbstractSeleniumTest {
|
||||
mapSettings = raceboard.openMapSettings();
|
||||
// Verify that mode settings are overridden by document settings
|
||||
Assert.assertFalse(mapSettings.isWindUp());
|
||||
// verify that document settings are able to override custom user settings by a system default value
|
||||
// Verify custom user settings are independent for modes
|
||||
Assert.assertFalse(mapSettings.isTransparentHoverlines());
|
||||
|
||||
//FIXME uncomment when START_ANALYSIS mode can be handled by remote CI server
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
allInOneErrorImportFileMissing=No file to import found!
|
||||
allInOneErrorUnknownImportMode=Unknown import mode!
|
||||
allInOneErrorMissingBoatClass=No boat class name found!
|
||||
allInOneErrorMissingRegattaClass=No regatta name found!
|
||||
allInOneErrorGPSDataImportFailed=Failed to import GPS data!
|
||||
allInOneErrorInvalidData=The regatta could not be resolved, please ensure the name is correct
|
||||
allInOneErrorInvalidRegattaName=Please enter a valid regatta name
|
||||
allInOneErrorInvalidImportMode=Not handled ImportMode
|
||||
allInOneErrorMultiSeries=There is more than one series in this regatta, cannot add race
|
||||
allInOneErrorInvalidSeries=There is no series in this regatta, cannot add race
|
||||
allInOneErrorSplitFleetNotSupported=The expedition importer cannot handle split fleet racing
|
||||
allInOneErrorInvalidRace=To add competitors, a race must be existing
|
||||
allInOneErrorInvalidLeaderBoardEventLink=The Event for the leaderboard could not be obtained, please ensure the leaderboard is properly attached to an leaderboard group that is attached to an event
|
||||
allInOneErrorInvalidLeaderBoard=The Leaderboard could not be resolved, please ensure a leaderboard for the regatta exists
|
||||
allInOneErrorSensorDataImportFailed=Failed to import sensor data!
|
||||
allInOneErrorBoatClassDeterminationFailed=Failed to determine boat class!
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
allInOneErrorImportFileMissing=Die Importdatei wurde nicht gefunden!
|
||||
allInOneErrorUnknownImportMode=Unbekannter Importmodus!
|
||||
allInOneErrorMissingBoatClass=Keine Bootsklasse gefunden!
|
||||
allInOneErrorMissingRegattaClass=Kein Regattaname gefunden!
|
||||
allInOneErrorGPSDataImportFailed=Fehler beim Import der GPS-Daten!
|
||||
allInOneErrorInvalidData=Die Regatta konnte nicht gefunden werden. Bitte sicherstellen, dass der Name gültig ist.
|
||||
allInOneErrorInvalidRegattaName=Bitte einen gültigen Regattanamen eingeben
|
||||
allInOneErrorInvalidImportMode=Nicht unterstützter Importmodus
|
||||
allInOneErrorMultiSeries=Es existiert mehr als eine Serie in der Regatta, Rennen kann nicht hinzugefügt werden
|
||||
allInOneErrorInvalidSeries=Es existiert keine Serie in der Regatta, Rennen kann nicht hinzugefügt werden
|
||||
allInOneErrorSplitFleetNotSupported=Der Expeditionimporter kann nicht in Regatten mit mehreren Flotten importieren
|
||||
allInOneErrorInvalidRace=Um Teilnehmer hinzuzufügen, muss ein Rennen existieren
|
||||
allInOneErrorInvalidLeaderBoardEventLink=Das Event welches zu der Rangliste gehört konnnte nicht bestimmt werden. Bitte sicherstellen, dass die Rangliste an einem Event registiert ist.
|
||||
allInOneErrorInvalidLeaderBoard=Die Rangliste konnte nicht aufgelöst werden. Bitte sicherstellen, dass eine Rangliste für die Regatta existiert.
|
||||
allInOneErrorSensorDataImportFailed=Fehler beim Import der Sensor-Daten!
|
||||
allInOneErrorBoatClassDeterminationFailed=Fehler beim Bestimmen der Bootsklasse
|
||||
+57
-12
@@ -1,8 +1,10 @@
|
||||
package com.sap.sailing.server.gateway.trackfiles.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -15,30 +17,37 @@ import org.apache.commons.fileupload.FileItem;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
import com.sap.sailing.domain.common.dto.ExpeditionAllInOneConstants;
|
||||
import com.sap.sailing.domain.common.dto.ExpeditionAllInOneConstants.ImportMode;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
|
||||
import com.sap.sailing.server.gateway.impl.AbstractFileUploadServlet;
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ExpeditionAllInOneImporter.ImporterResult;
|
||||
import com.sap.sse.i18n.ResourceBundleStringMessages;
|
||||
import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl;
|
||||
import com.sap.sse.util.ServiceTrackerFactory;
|
||||
|
||||
/**
|
||||
* Import servlet for sensor data files. Importers are located through the OSGi service registry and matched against the
|
||||
* name provided by the upload form.
|
||||
*
|
||||
* @see ExpeditionAllInOneImporter
|
||||
*/
|
||||
public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet {
|
||||
private static final long serialVersionUID = 1120226743039934620L;
|
||||
private static final Logger logger = Logger.getLogger(ExpeditionAllInOneImportServlet.class.getName());
|
||||
|
||||
private static final String REQUEST_PARAMETER_BOAT_CLASS = "boatClass";
|
||||
private static final String ERROR_MESSAGE_IMPORT_FILE_MISSING = "No file to import found!";
|
||||
private static final String ERROR_MESSAGE_BOAT_CLASS_MISSING = "No boat class name found!";
|
||||
private static final String STRING_MESSAGES_BASE_NAME = "stringmessages/StringMessages";
|
||||
|
||||
private ServiceTracker<RaceLogTrackingAdapterFactory, RaceLogTrackingAdapterFactory> raceLogTrackingAdapterTracker;
|
||||
private ResourceBundleStringMessages serverStringMessages;
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
raceLogTrackingAdapterTracker = ServiceTrackerFactory.createAndOpen(getContext(),
|
||||
RaceLogTrackingAdapterFactory.class);
|
||||
serverStringMessages = new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME,
|
||||
this.getClass().getClassLoader(), StandardCharsets.UTF_8.name());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,25 +62,63 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet {
|
||||
String fileName = null;
|
||||
FileItem fileItem = null;
|
||||
String boatClassName = null;
|
||||
String regattaName = null;
|
||||
String importModeName = null;
|
||||
String localeName = null;
|
||||
for (FileItem fi : fileItems) {
|
||||
if (!fi.isFormField()) {
|
||||
fileName = fi.getName();
|
||||
fileItem = fi;
|
||||
} else if (fi.getFieldName() != null) {
|
||||
if (REQUEST_PARAMETER_BOAT_CLASS.equals(fi.getFieldName())) {
|
||||
if (ExpeditionAllInOneConstants.REQUEST_PARAMETER_BOAT_CLASS.equals(fi.getFieldName())) {
|
||||
boatClassName = fi.getString();
|
||||
}
|
||||
if (ExpeditionAllInOneConstants.REQUEST_PARAMETER_REGATTA_NAME.equals(fi.getFieldName())) {
|
||||
regattaName = fi.getString();
|
||||
}
|
||||
if (ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_MODE.equals(fi.getFieldName())) {
|
||||
importModeName = fi.getString();
|
||||
}
|
||||
if (ExpeditionAllInOneConstants.REQUEST_PARAMETER_LOCALE.equals(fi.getFieldName())) {
|
||||
localeName = fi.getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Locale uiLocale;
|
||||
if (localeName == null) {
|
||||
uiLocale = Locale.ENGLISH;
|
||||
} else {
|
||||
try {
|
||||
uiLocale = Locale.forLanguageTag(localeName);
|
||||
} catch (Exception e) {
|
||||
uiLocale = Locale.ENGLISH;
|
||||
}
|
||||
}
|
||||
if (fileItem == null) {
|
||||
throw new AllinOneImportException(ERROR_MESSAGE_IMPORT_FILE_MISSING);
|
||||
throw new AllinOneImportException(serverStringMessages.get(uiLocale, "allInOneErrorImportFileMissing"));
|
||||
}
|
||||
if (boatClassName == null || boatClassName.isEmpty()) {
|
||||
throw new AllinOneImportException(ERROR_MESSAGE_BOAT_CLASS_MISSING);
|
||||
final ImportMode importMode;
|
||||
if (importModeName == null) {
|
||||
importMode = ImportMode.NEW_EVENT;
|
||||
} else {
|
||||
try {
|
||||
importMode = ImportMode.valueOf(importModeName);
|
||||
} catch (Exception e) {
|
||||
throw new AllinOneImportException(serverStringMessages.get(uiLocale, "allInOneErrorUnknownImportMode"));
|
||||
}
|
||||
}
|
||||
importerResult = new ExpeditionAllInOneImporter(getService(),
|
||||
if (importMode == ImportMode.NEW_EVENT) {
|
||||
if (boatClassName == null || boatClassName.isEmpty()) {
|
||||
throw new AllinOneImportException(serverStringMessages.get(uiLocale, "allInOneErrorMissingBoatClass"));
|
||||
}
|
||||
} else {
|
||||
if (regattaName == null || regattaName.isEmpty()) {
|
||||
throw new AllinOneImportException(serverStringMessages.get(uiLocale, "allInOneErrorMissingRegattaClass"));
|
||||
}
|
||||
}
|
||||
importerResult = new ExpeditionAllInOneImporter(serverStringMessages, uiLocale, getService(),
|
||||
raceLogTrackingAdapterTracker.getService().getAdapter(getService().getBaseDomainFactory()),
|
||||
getServiceFinderFactory(), getContext()).importFiles(fileName, fileItem, boatClassName);
|
||||
getServiceFinderFactory(), getContext()).importFiles(fileName, fileItem, boatClassName, importMode, regattaName);
|
||||
} catch (AllinOneImportException e) {
|
||||
importerResult = new ImporterResult(e, e.additionalErrors);
|
||||
logger.log(Level.SEVERE, e.getMessage());
|
||||
@@ -89,9 +136,7 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet {
|
||||
json.put("leaderboardName", importerResult.leaderboardName);
|
||||
json.put("leaderboardGroupName", importerResult.leaderboardGroupName);
|
||||
json.put("regattaName", importerResult.regattaName);
|
||||
json.put("raceName", importerResult.raceName);
|
||||
json.put("raceColumnName", importerResult.raceColumnName);
|
||||
json.put("fleetName", importerResult.fleetName);
|
||||
json.put("raceNameRaceColumnNameFleetNameList", ImportResultSerializer.serializeRaceList(importerResult.raceNameRaceColumnNameFleetnameList));
|
||||
json.put("errors", ImportResultSerializer.serializeErrorList(importerResult.errorList));
|
||||
json.put("gpsDeviceIds", ImportResultSerializer.serializeTrackList(importerResult.importGpsFixData));
|
||||
json.put("sensorDeviceIds", ImportResultSerializer.serializeTrackList(importerResult.importSensorFixData));
|
||||
|
||||
+358
-83
@@ -12,11 +12,17 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.commons.fileupload.FileItem;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
@@ -25,31 +31,45 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImp
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogDenoteForTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogStartTrackingEventImpl;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
import com.sap.sailing.domain.common.Placemark;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.RankingMetrics;
|
||||
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.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.dto.ExpeditionAllInOneConstants.ImportMode;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
|
||||
import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
|
||||
import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException;
|
||||
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
|
||||
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
||||
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
|
||||
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
|
||||
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl;
|
||||
import com.sap.sailing.domain.trackimport.DoubleVectorFixImporter;
|
||||
import com.sap.sailing.domain.trackimport.GPSFixImporter;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.RaceHandle;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.WindTrack;
|
||||
import com.sap.sailing.geocoding.ReverseGeocoder;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ImportResultDTO.ErrorImportDTO;
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ImportResultDTO.TrackImportDTO;
|
||||
@@ -62,48 +82,91 @@ import com.sap.sailing.server.operationaltransformation.CreateLeaderboardGroup;
|
||||
import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard;
|
||||
import com.sap.sailing.server.operationaltransformation.UpdateEvent;
|
||||
import com.sap.sailing.server.util.WaitForTrackedRaceUtil;
|
||||
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.TypeBasedServiceFinderFactory;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sse.i18n.ResourceBundleStringMessages;
|
||||
|
||||
/**
|
||||
* Importer for expedition data that imports all available data for a boat:
|
||||
* <ul>
|
||||
* <li>{@link GPSFixMoving} and {@link BravoExtendedFix} instances are imported as a distinct track that needs to be
|
||||
* mapped to a specific competitor afterwards</li>
|
||||
* <li>Wind fixes are being imported as a new {@link WindTrack}</li>
|
||||
* </ul>
|
||||
* There are several {@link ImportMode ImportModes} which have specific preconditions and activate different importing
|
||||
* behavior:
|
||||
* <ul>
|
||||
* <li>{@link ImportMode#NEW_EVENT} requires a boatclass name to be given. In this case new {@link Event},
|
||||
* {@link Regatta} and {@link RegattaLeaderboard} entities are created with one {@link RaceColumn} for a new
|
||||
* {@link TrackedRace} to be the target of the imported data. The names of the created entities as well as the created
|
||||
* {@link RaceColumn} and {@link WindSource} are generated from the name of the given {@link FileItem}.</li>
|
||||
* <li>{@link ImportMode#NEW_COMPETITOR} requires a regatta name to be given. In this case the wind data is being
|
||||
* imported as new {@link WindSource} to any existing race of the given regatta. The name of the {@link WindSource} is
|
||||
* determined from the name of the given {@link FileItem}. No new entities are created.</li>
|
||||
* <li>{@link ImportMode#NEW_RACE} requires a regatta name to be given. In this case the a new {@link RaceColumn} is
|
||||
* created in the last existing {@link Series}. A new {@link WindSource} is added to the {@link TrackedRace} associated
|
||||
* with the newly added {@link RaceColumn}. This mode does not support importing in cases where fleet racing is used by
|
||||
* a {@link Regatta}.</li>
|
||||
* </ul>
|
||||
* The imported {@link GPSFixMoving} and {@link BravoExtendedFix} tracks aren't mapped to a {@link Competitor} by the
|
||||
* importer. Instead the IDs of the imported tracks are contained in the result and are expected to be mapped by the
|
||||
* user afterwards.
|
||||
*
|
||||
* This importer is intended to be used by {@link ExpeditionAllInOneImportServlet}.
|
||||
*/
|
||||
public class ExpeditionAllInOneImporter {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ExpeditionAllInOneImporter.class.getName());
|
||||
|
||||
private static final String ERROR_MESSAGE_GPS_DATA_IMPORT_FAILED = "Failed to import GPS data!";
|
||||
private static final String ERROR_MESSAGE_SENSOR_DATA_IMPORT_FAILED = "Failed to import sensor data!";
|
||||
private static final String ERROR_MESSAGE_BOAT_CLASS_DETERMINATION_FAILED = "Failed to determine boat class!";
|
||||
private static final double VENUE_RANGE_CHECK = 10;
|
||||
|
||||
private final RacingEventService service;
|
||||
private final RaceLogTrackingAdapter adapter;
|
||||
private final TypeBasedServiceFinderFactory serviceFinderFactory;
|
||||
private final BundleContext context;
|
||||
|
||||
private ResourceBundleStringMessages serverStringMessages;
|
||||
private Locale uiLocale;
|
||||
|
||||
public static class ImporterResult {
|
||||
final UUID eventId;
|
||||
final String leaderboardName, leaderboardGroupName, regattaName, raceName, raceColumnName, fleetName;
|
||||
final List<Triple<String, String, String>> raceNameRaceColumnNameFleetnameList = new ArrayList<>();
|
||||
final String leaderboardName, leaderboardGroupName, regattaName;
|
||||
final List<TrackImportDTO> importGpsFixData, importSensorFixData;
|
||||
final String sensorFixImporterType;
|
||||
final List<ErrorImportDTO> errorList = new ArrayList<>();
|
||||
|
||||
public ImporterResult(String error) {
|
||||
this(null, "", "", "", Collections.emptyList(), Collections.emptyList(),
|
||||
Collections.emptyList(), "", Collections.emptyList());
|
||||
errorList.add(new ErrorImportDTO(error));
|
||||
}
|
||||
|
||||
public ImporterResult(Throwable exception, List<ErrorImportDTO> additionalErrors) {
|
||||
this(null, "", "", new RegattaNameAndRaceName("", ""), "", "", Collections.emptyList(),
|
||||
Collections.emptyList(), "", additionalErrors);
|
||||
this.errorList.add(new ErrorImportDTO(exception.getClass().getName(), exception.getMessage()));
|
||||
this(null, "", "","", Collections.emptyList(), Collections.emptyList(),
|
||||
Collections.emptyList(), "", Collections.emptyList());
|
||||
errorList.add(new ErrorImportDTO(exception.getClass().getName(), exception.getMessage()));
|
||||
if (additionalErrors != null) {
|
||||
errorList.addAll(additionalErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private ImporterResult(final UUID eventId, final String leaderboardName, String leaderboardGroupName,
|
||||
final RegattaAndRaceIdentifier regattaAndRaceIdentifier, final String raceColumnName,
|
||||
final String fleetName, final List<TrackImportDTO> importGpsFixData,
|
||||
final List<TrackImportDTO> importSensorFixData, final String sensorFixImporterType,
|
||||
List<ErrorImportDTO> errors) {
|
||||
final String regattaName, List<Triple<String, String, String>> raceNameRaceColumnNameFleetnameList,
|
||||
final List<TrackImportDTO> importGpsFixData, final List<TrackImportDTO> importSensorFixData,
|
||||
final String sensorFixImporterType, List<ErrorImportDTO> errors) {
|
||||
this.eventId = eventId;
|
||||
this.leaderboardName = leaderboardName;
|
||||
this.leaderboardGroupName = leaderboardGroupName;
|
||||
this.regattaName = regattaAndRaceIdentifier.getRegattaName();
|
||||
this.raceName = regattaAndRaceIdentifier.getRaceName();
|
||||
this.raceColumnName = raceColumnName;
|
||||
this.fleetName = fleetName;
|
||||
this.regattaName = regattaName;
|
||||
if (raceNameRaceColumnNameFleetnameList != null) {
|
||||
this.raceNameRaceColumnNameFleetnameList.addAll(raceNameRaceColumnNameFleetnameList);
|
||||
}
|
||||
this.importGpsFixData = importGpsFixData;
|
||||
this.importSensorFixData = importSensorFixData;
|
||||
this.sensorFixImporterType = sensorFixImporterType;
|
||||
@@ -111,8 +174,10 @@ public class ExpeditionAllInOneImporter {
|
||||
}
|
||||
}
|
||||
|
||||
public ExpeditionAllInOneImporter(final RacingEventService service, RaceLogTrackingAdapter adapter,
|
||||
public ExpeditionAllInOneImporter(ResourceBundleStringMessages serverStringMessages, Locale uiLocale, final RacingEventService service, RaceLogTrackingAdapter adapter,
|
||||
final TypeBasedServiceFinderFactory serviceFinderFactory, final BundleContext context) {
|
||||
this.serverStringMessages = serverStringMessages;
|
||||
this.uiLocale = uiLocale;
|
||||
this.service = service;
|
||||
this.adapter = adapter;
|
||||
this.serviceFinderFactory = serviceFinderFactory;
|
||||
@@ -120,36 +185,16 @@ public class ExpeditionAllInOneImporter {
|
||||
}
|
||||
|
||||
public ImporterResult importFiles(final String filenameWithSuffix, final FileItem fileItem,
|
||||
final String boatClassName) throws AllinOneImportException {
|
||||
final String boatClassName, ImportMode importMode, String existingRegattaName) throws AllinOneImportException {
|
||||
final List<ErrorImportDTO> errors = new ArrayList<>();
|
||||
final String importTimeString = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now(ZoneOffset.UTC));
|
||||
|
||||
final String filename = ExpeditionImportFilenameUtils.truncateFilenameExtentions(filenameWithSuffix);
|
||||
final String filenameWithDateTimeSuffix = filename + "_" + importTimeString;
|
||||
final String eventName = filenameWithDateTimeSuffix;
|
||||
final String description = MessageFormat.format("Event imported from expedition file ''{0}'' on {1}",
|
||||
filenameWithSuffix, importTimeString);
|
||||
final String leaderboardGroupName = filenameWithDateTimeSuffix;
|
||||
final String regattaNameAndleaderboardName = filenameWithDateTimeSuffix;
|
||||
final RegattaIdentifier regattaIdentifier = new RegattaName(filenameWithDateTimeSuffix);
|
||||
final String raceColumnName = filename;
|
||||
final String trackedRaceName = filenameWithDateTimeSuffix;
|
||||
final String courseAreaName = "Default";
|
||||
final UUID courseAreaId = UUID.randomUUID();
|
||||
// This is just the default used in the UI
|
||||
final Double buoyZoneRadiusInHullLengths = 3.0;
|
||||
// TODO is this a proper id for the WindSource used here?
|
||||
final String windSourceId = filenameWithDateTimeSuffix;
|
||||
|
||||
// TODO guess venue based on the reverse geocoder?
|
||||
final String venueName = filename;
|
||||
|
||||
final String fleetName = LeaderboardNameConstants.DEFAULT_FLEET_NAME;
|
||||
final String seriesName = Series.DEFAULT_NAME;
|
||||
|
||||
// TODO wild guess...
|
||||
final ScoringSchemeType scoringSchemeType = ScoringSchemeType.LOW_POINT;
|
||||
final RankingMetrics rankingMetric = RankingMetrics.ONE_DESIGN;
|
||||
final int[] discardThresholds = new int[0];
|
||||
|
||||
final ImportResultDTO jsonHolderForGpsFixImport = new ImportResultDTO(logger);
|
||||
@@ -157,12 +202,14 @@ public class ExpeditionAllInOneImporter {
|
||||
try {
|
||||
new TrackFilesImporter(service, serviceFinderFactory, context).importFixes(jsonHolderForGpsFixImport,
|
||||
GPSFixImporter.EXPEDITION_TYPE, filesForGpsFixImport);
|
||||
this.ensureSuccessfulImport(jsonHolderForGpsFixImport, ERROR_MESSAGE_GPS_DATA_IMPORT_FAILED);
|
||||
this.ensureSuccessfulImport(jsonHolderForGpsFixImport,
|
||||
serverStringMessages.get(uiLocale, "allInOneErrorGPSDataImportFailed"));
|
||||
} catch (IOException e1) {
|
||||
errors.addAll(jsonHolderForGpsFixImport.getErrorList());
|
||||
throw new AllinOneImportException(e1, errors);
|
||||
}
|
||||
errors.addAll(jsonHolderForGpsFixImport.getErrorList());
|
||||
|
||||
|
||||
final ImportResultDTO jsonHolderForSensorFixImport = new ImportResultDTO(logger);
|
||||
final String sensorFixImporterType = DoubleVectorFixImporter.EXPEDITION_EXTENDED_TYPE;
|
||||
@@ -171,7 +218,8 @@ public class ExpeditionAllInOneImporter {
|
||||
try {
|
||||
new SensorDataImporter(service, context).importFiles(false, jsonHolderForSensorFixImport,
|
||||
importerNamesAndFilesForSensorFixImport);
|
||||
this.ensureSuccessfulImport(jsonHolderForSensorFixImport, ERROR_MESSAGE_SENSOR_DATA_IMPORT_FAILED);
|
||||
this.ensureSuccessfulImport(jsonHolderForSensorFixImport,
|
||||
serverStringMessages.get(uiLocale, "allInOneErrorSensorDataImportFailed"));
|
||||
} catch (IOException e1) {
|
||||
errors.addAll(jsonHolderForSensorFixImport.getErrorList());
|
||||
throw new AllinOneImportException(e1, errors);
|
||||
@@ -194,14 +242,244 @@ public class ExpeditionAllInOneImporter {
|
||||
lastFixAt = deviceTrackEnd;
|
||||
}
|
||||
}
|
||||
|
||||
final TimePoint eventStartDate = firstFixAt;
|
||||
final TimePoint eventEndDate = lastFixAt;
|
||||
|
||||
final UUID eventId;
|
||||
final String leaderboardGroupName;
|
||||
final String regattaNameAndleaderboardName;
|
||||
final List<Triple<String, String, String>> raceNameRaceColumnNameFleetnameList = new ArrayList<>();
|
||||
final List<DynamicTrackedRace> trackedRaces = new ArrayList<>();
|
||||
if (importMode == ImportMode.NEW_EVENT) {
|
||||
leaderboardGroupName = filenameWithDateTimeSuffix;
|
||||
regattaNameAndleaderboardName = filenameWithDateTimeSuffix;
|
||||
String raceColumnName = filename;
|
||||
eventId = UUID.randomUUID();
|
||||
String fleetName = LeaderboardNameConstants.DEFAULT_FLEET_NAME;
|
||||
final DynamicTrackedRace trackedRace = createEventStructureWithASingleRaceAndTrackIt(filenameWithSuffix, boatClassName, errors, importTimeString, filename,
|
||||
filenameWithDateTimeSuffix, trackedRaceName, discardThresholds, firstFixAt, lastFixAt,
|
||||
eventStartDate, eventEndDate, eventId, leaderboardGroupName, regattaNameAndleaderboardName,
|
||||
fleetName, raceColumnName);
|
||||
trackedRaces.add(trackedRace);
|
||||
raceNameRaceColumnNameFleetnameList.add(new Triple<>(trackedRaceName, raceColumnName, fleetName));
|
||||
updateVenueName(filename, jsonHolderForGpsFixImport.getImportResult(), eventId);
|
||||
} else {
|
||||
regattaNameAndleaderboardName = existingRegattaName;
|
||||
if (existingRegattaName != null && !existingRegattaName.isEmpty()) {
|
||||
final Regatta regatta = service.getRegattaByName(existingRegattaName);
|
||||
if (regatta == null) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidData"));
|
||||
}
|
||||
final Leaderboard leaderboard = service.getLeaderboardByName(existingRegattaName);
|
||||
if (leaderboard == null || !(leaderboard instanceof RegattaLeaderboard)) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidLeaderBoard"));
|
||||
}
|
||||
final RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard;
|
||||
|
||||
final Pair<Event, LeaderboardGroup> foundEventAndLeaderboardGroup = findEventAndLeaderboardGroupForExistingLeaderboard(leaderboard);
|
||||
|
||||
if (foundEventAndLeaderboardGroup == null) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidLeaderBoardEventLink"));
|
||||
}
|
||||
|
||||
eventId = foundEventAndLeaderboardGroup.getA().getId();
|
||||
ensureEventLongEnough(firstFixAt, lastFixAt, eventId);
|
||||
leaderboardGroupName = foundEventAndLeaderboardGroup.getB().getName();
|
||||
|
||||
if (importMode == ImportMode.NEW_COMPETITOR) {
|
||||
final Iterable<RaceColumn> raceColumns = regattaLeaderboard.getRaceColumns();
|
||||
if (Util.isEmpty(raceColumns)) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidRace"));
|
||||
}
|
||||
try {
|
||||
for (RaceColumn raceColumn : raceColumns) {
|
||||
final Iterable<? extends Fleet> fleets = raceColumn.getFleets();
|
||||
if (Util.size(fleets) != 1) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorSplitFleetNotSupported"));
|
||||
}
|
||||
final Fleet fleet = fleets.iterator().next();
|
||||
DynamicTrackedRace trackedRaceForColumn = (DynamicTrackedRace) raceColumn.getTrackedRace(fleet);
|
||||
if (trackedRaceForColumn == null) {
|
||||
trackedRaceForColumn = trackRace(regattaLeaderboard, raceColumn, fleet);
|
||||
}
|
||||
trackedRaces.add(trackedRaceForColumn);
|
||||
raceNameRaceColumnNameFleetnameList
|
||||
.add(new Triple<>(trackedRaceForColumn.getRaceIdentifier().getRaceName(),
|
||||
raceColumn.getName(), fleet.getName()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new AllinOneImportException(e, errors);
|
||||
}
|
||||
} else if (importMode == ImportMode.NEW_RACE) {
|
||||
final Iterable<? extends Series> seriesInRegatta = regatta.getSeries();
|
||||
if (Util.isEmpty(seriesInRegatta)) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidSeries"));
|
||||
}
|
||||
final Series series = Util.get(seriesInRegatta, Util.size(seriesInRegatta) - 1);
|
||||
final Iterable<? extends Fleet> fleets = series.getFleets();
|
||||
if (Util.size(fleets) != 1) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorMultiSeries"));
|
||||
}
|
||||
final Fleet fleet = fleets.iterator().next();
|
||||
final String fleetName = fleet.getName();
|
||||
// When uploading files with identical name, the second RaceColumn will be named with the upload time in its name
|
||||
final String raceColumnName = regatta.getRaceColumnByName(filename) == null ? filename : filenameWithDateTimeSuffix;
|
||||
final RaceColumn raceColumn = service.apply(new AddColumnToSeries(regatta.getRegattaIdentifier(), series.getName(), raceColumnName));
|
||||
|
||||
final Event event = service.addEvent(eventName, description, eventStartDate, eventEndDate, venueName, true,
|
||||
UUID.randomUUID());
|
||||
final DynamicTrackedRace trackedRace = createTrackedRaceAndSetupRaceTimes(errors, trackedRaceName, firstFixAt, lastFixAt, regatta, regattaLeaderboard,
|
||||
raceColumn, fleet);
|
||||
trackedRaces.add(trackedRace);
|
||||
raceNameRaceColumnNameFleetnameList.add(new Triple<>(trackedRaceName, raceColumnName, fleetName));
|
||||
} else {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidImportMode") + importMode);
|
||||
}
|
||||
} else {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidRegattaName"));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final WindImportResult windImportResult = new AbstractWindImporter.WindImportResult();
|
||||
final WindSourceWithAdditionalID windSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION,
|
||||
windSourceId);
|
||||
final Map<InputStream, String> streamsWithFilenames = new HashMap<>();
|
||||
streamsWithFilenames.put(fileItem.getInputStream(), filenameWithSuffix);
|
||||
new WindImporter().importWindToWindSourceAndTrackedRaces(service, windImportResult, windSource,
|
||||
trackedRaces, streamsWithFilenames);
|
||||
|
||||
return new ImporterResult(eventId, regattaNameAndleaderboardName, leaderboardGroupName,
|
||||
regattaNameAndleaderboardName, raceNameRaceColumnNameFleetnameList,
|
||||
jsonHolderForGpsFixImport.getImportResult(), jsonHolderForSensorFixImport.getImportResult(),
|
||||
sensorFixImporterType, errors);
|
||||
} catch (Exception e) {
|
||||
throw new AllinOneImportException(e, errors);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureEventLongEnough(TimePoint firstFixAt, TimePoint lastFixAt, UUID eventId) {
|
||||
Event event = service.getEvent(eventId);
|
||||
TimePoint startDate = event.getStartDate();
|
||||
if(firstFixAt.before(startDate)) {
|
||||
startDate = firstFixAt;
|
||||
}
|
||||
TimePoint endDate = event.getEndDate();
|
||||
if(lastFixAt.after(endDate)) {
|
||||
endDate = lastFixAt;
|
||||
}
|
||||
Iterable<UUID> leaderboardGroups = StreamSupport.stream(event.getLeaderboardGroups().spliterator(), false).map(t -> t.getId()).collect(Collectors.toList());
|
||||
|
||||
service.apply(new UpdateEvent(event.getId(), event.getName(), event.getDescription(), startDate,
|
||||
endDate, event.getVenue().getName(), event.isPublic(),
|
||||
leaderboardGroups, event.getOfficialWebsiteURL(), event.getBaseURL(),
|
||||
event.getSailorsInfoWebsiteURLs(), event.getImages(), event.getVideos(), event.getWindFinderReviewedSpotsCollectionIds()));
|
||||
}
|
||||
|
||||
private Pair<Event, LeaderboardGroup> findEventAndLeaderboardGroupForExistingLeaderboard(final Leaderboard leaderboard) {
|
||||
for (Event event : service.getAllEvents()) {
|
||||
for (LeaderboardGroup leaderboardGroup : event.getLeaderboardGroups()) {
|
||||
for (Leaderboard lb : leaderboardGroup.getLeaderboards()) {
|
||||
if (lb.equals(leaderboard)) {
|
||||
return new Pair<>(event, leaderboardGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DynamicTrackedRace createEventStructureWithASingleRaceAndTrackIt(final String filenameWithSuffix, final String boatClassName,
|
||||
final List<ErrorImportDTO> errors, final String importTimeString, final String filename,
|
||||
final String filenameWithDateTimeSuffix, final String trackedRaceName, final int[] discardThresholds,
|
||||
TimePoint firstFixAt, TimePoint lastFixAt, final TimePoint eventStartDate, final TimePoint eventEndDate,
|
||||
final UUID eventId, final String leaderboardGroupName, final String regattaNameAndleaderboardName,
|
||||
final String fleetName, final String raceColumnName) throws AllinOneImportException {
|
||||
final DynamicTrackedRace trackedRace;
|
||||
|
||||
final String eventName = filenameWithDateTimeSuffix;
|
||||
final String description = MessageFormat.format("Event imported from expedition file ''{0}'' on {1}",
|
||||
filenameWithSuffix, importTimeString);
|
||||
final RegattaIdentifier regattaIdentifier = new RegattaName(filenameWithDateTimeSuffix);
|
||||
// This is just the default used in the UI
|
||||
final Double buoyZoneRadiusInHullLengths = 3.0;
|
||||
|
||||
final String seriesName = Series.DEFAULT_NAME;
|
||||
|
||||
final Event event = service.addEvent(eventName, description, eventStartDate, eventEndDate, filename, true,
|
||||
eventId);
|
||||
|
||||
final UUID courseAreaId = addDefaultCourseArea(event);
|
||||
|
||||
final Regatta regatta = createRegattaWithOneRaceColumn(boatClassName, regattaNameAndleaderboardName,
|
||||
fleetName, raceColumnName, regattaIdentifier, courseAreaId, buoyZoneRadiusInHullLengths,
|
||||
seriesName);
|
||||
final RegattaLeaderboard regattaLeaderboard = service.apply(new CreateRegattaLeaderboard(regattaIdentifier, null,
|
||||
discardThresholds));
|
||||
|
||||
createLeaderboardGroupAndAddItToTheEvent(leaderboardGroupName, regattaNameAndleaderboardName, description, event);
|
||||
|
||||
final RaceColumn raceColumn = regattaLeaderboard.getRaceColumns().iterator().next();
|
||||
final Fleet fleet = raceColumn.getFleets().iterator().next();
|
||||
|
||||
trackedRace = createTrackedRaceAndSetupRaceTimes(errors, trackedRaceName, firstFixAt, lastFixAt, regatta, regattaLeaderboard,
|
||||
raceColumn, fleet);
|
||||
return trackedRace;
|
||||
}
|
||||
|
||||
private void updateVenueName(String filename, List<TrackImportDTO> list, UUID eventId) {
|
||||
for (TrackImportDTO f : list) {
|
||||
TrackFileImportDeviceIdentifier deviceIdentifier = TrackFileImportDeviceIdentifierImpl
|
||||
.getOrCreate(f.getDevice());
|
||||
final TimePoint end = f.getRange().to();
|
||||
try {
|
||||
final CompletableFuture<GPSFix> waitForFix = new CompletableFuture<>();
|
||||
service.getSensorFixStore().loadFixes(waitForFix::complete, deviceIdentifier, end, end, true);
|
||||
GPSFix gpsFix = waitForFix.getNow(null);
|
||||
if (gpsFix != null) {
|
||||
Position pos = gpsFix.getPosition();
|
||||
if (pos != null) {
|
||||
try {
|
||||
Placemark reverseVenue = ReverseGeocoder.INSTANCE.getPlacemarkFirst(pos, VENUE_RANGE_CHECK,
|
||||
new Placemark.ByPopulationDistanceRatio(pos));
|
||||
if (reverseVenue != null) {
|
||||
String newVenueName = reverseVenue.getName();
|
||||
Event event = service.getEvent(eventId);
|
||||
Iterable<UUID> leaderboardGroups = StreamSupport
|
||||
.stream(event.getLeaderboardGroups().spliterator(), false).map(t -> t.getId())
|
||||
.collect(Collectors.toList());
|
||||
service.apply(new UpdateEvent(event.getId(), event.getName(), event.getDescription(),
|
||||
event.getStartDate(), event.getEndDate(), newVenueName, event.isPublic(),
|
||||
leaderboardGroups, event.getOfficialWebsiteURL(), event.getBaseURL(),
|
||||
event.getSailorsInfoWebsiteURLs(), event.getImages(), event.getVideos(),
|
||||
event.getWindFinderReviewedSpotsCollectionIds()));
|
||||
break;
|
||||
}
|
||||
} catch (IOException | ParseException e) {
|
||||
logger.log(Level.WARNING, "Could not reverse determine location " + pos, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoCorrespondingServiceRegisteredException | TransformationException e) {
|
||||
logger.log(Level.WARNING, "Could not reverse determine location", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private UUID addDefaultCourseArea(final Event event) {
|
||||
final String courseAreaName = "Default";
|
||||
final UUID courseAreaId = UUID.randomUUID();
|
||||
service.addCourseAreas(event.getId(), new String[] { courseAreaName }, new UUID[] { courseAreaId });
|
||||
return courseAreaId;
|
||||
}
|
||||
|
||||
private Regatta createRegattaWithOneRaceColumn(final String boatClassName, final String regattaNameAndleaderboardName, final String fleetName,
|
||||
final String raceColumnName, final RegattaIdentifier regattaIdentifier, final UUID courseAreaId,
|
||||
final Double buoyZoneRadiusInHullLengths, final String seriesName) throws AllinOneImportException {
|
||||
|
||||
// TODO wild guess...
|
||||
final ScoringSchemeType scoringSchemeType = ScoringSchemeType.LOW_POINT;
|
||||
final RankingMetrics rankingMetric = RankingMetrics.ONE_DESIGN;
|
||||
|
||||
final Regatta regatta;
|
||||
final ScoringScheme scoringScheme = service.getBaseDomainFactory().createScoringScheme(scoringSchemeType);
|
||||
|
||||
final LinkedHashMap<String, SeriesCreationParametersDTO> seriesCreationParameters = new LinkedHashMap<>();
|
||||
@@ -214,73 +492,70 @@ public class ExpeditionAllInOneImporter {
|
||||
final RegattaCreationParametersDTO regattaCreationParameters = new RegattaCreationParametersDTO(
|
||||
seriesCreationParameters);
|
||||
|
||||
final Regatta regatta = service.apply(new AddSpecificRegatta(regattaNameAndleaderboardName, boatClassName,
|
||||
/* can boats of competitors change */ false,
|
||||
/* start date */ null, /* end date */ null, UUID.randomUUID(),
|
||||
regatta = service.apply(new AddSpecificRegatta(regattaNameAndleaderboardName, boatClassName,
|
||||
/* can boats of competitors change */ false,
|
||||
/* start date */ null, /* end date */ null, UUID.randomUUID(),
|
||||
regattaCreationParameters, true, scoringScheme, courseAreaId, buoyZoneRadiusInHullLengths, true,
|
||||
false, rankingMetric));
|
||||
this.ensureBoatClassDetermination(regatta);
|
||||
service.apply(new AddColumnToSeries(regattaIdentifier, seriesName, raceColumnName));
|
||||
final RegattaLeaderboard regattaLeaderboard = service.apply(new CreateRegattaLeaderboard(regattaIdentifier, null,
|
||||
discardThresholds));
|
||||
return regatta;
|
||||
}
|
||||
|
||||
private void createLeaderboardGroupAndAddItToTheEvent(final String leaderboardGroupName, final String regattaNameAndleaderboardName,
|
||||
final String description, final Event event) {
|
||||
final LeaderboardGroup leaderboardGroup = service.apply(new CreateLeaderboardGroup(leaderboardGroupName,
|
||||
description, null, false, Collections.singletonList(regattaNameAndleaderboardName), null, null));
|
||||
service.apply(new UpdateEvent(event.getId(), event.getName(), event.getDescription(), event.getStartDate(),
|
||||
event.getEndDate(), event.getVenue().getName(), event.isPublic(),
|
||||
Collections.singleton(leaderboardGroup.getId()), event.getOfficialWebsiteURL(), event.getBaseURL(),
|
||||
event.getSailorsInfoWebsiteURLs(), event.getImages(), event.getVideos(), event.getWindFinderReviewedSpotsCollectionIds()));
|
||||
}
|
||||
|
||||
final RaceColumn raceColumn = regattaLeaderboard.getRaceColumns().iterator().next();
|
||||
final Fleet fleet = raceColumn.getFleets().iterator().next();
|
||||
|
||||
private DynamicTrackedRace createTrackedRaceAndSetupRaceTimes(final List<ErrorImportDTO> errors,
|
||||
final String trackedRaceName, TimePoint firstFixAt, TimePoint lastFixAt, final Regatta regatta,
|
||||
final RegattaLeaderboard regattaLeaderboard, final RaceColumn raceColumn, final Fleet fleet)
|
||||
throws AllinOneImportException {
|
||||
final RaceLog raceLog = raceColumn.getRaceLog(fleet);
|
||||
|
||||
final AbstractLogEventAuthor author = service.getServerAuthor();
|
||||
|
||||
final TimePoint startOfTracking = firstFixAt;
|
||||
final TimePoint endOfTracking = lastFixAt;
|
||||
raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTracking, author, raceLog.getCurrentPassId()));
|
||||
raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTracking, author, raceLog.getCurrentPassId()));
|
||||
// TODO explicitly set startOfRace?
|
||||
|
||||
try {
|
||||
TimePoint startTrackingTimePoint = MillisecondsTimePoint.now();
|
||||
// this ensures that the events consistently have different timepoints to ensure a consistent result of the state analysis
|
||||
// that's why we can't jus call adapter.denoteRaceForRaceLogTracking
|
||||
// this ensures that the events consistently have different timepoints to ensure a consistent result of the
|
||||
// state analysis
|
||||
// that's why we can't just call adapter.denoteRaceForRaceLogTracking
|
||||
final TimePoint denotationTimePoint = startTrackingTimePoint.minus(1);
|
||||
raceLog.add(new RaceLogDenoteForTrackingEventImpl(denotationTimePoint,
|
||||
service.getServerAuthor(), raceLog.getCurrentPassId(), trackedRaceName, regatta.getBoatClass(), UUID.randomUUID()));
|
||||
|
||||
raceLog.add(new RaceLogStartTrackingEventImpl(startTrackingTimePoint, author, raceLog.getCurrentPassId()));
|
||||
|
||||
final RaceHandle raceHandle = adapter.startTracking(service, regattaLeaderboard, raceColumn, fleet,
|
||||
/* trackWind */ true, /* correctWindDirectionByMagneticDeclination */ true);
|
||||
|
||||
// wait for the RaceDefinition to be created
|
||||
raceHandle.getRace();
|
||||
|
||||
final DynamicTrackedRace trackedRace = WaitForTrackedRaceUtil.waitForTrackedRace(raceColumn, fleet, 10);
|
||||
if (trackedRace == null) {
|
||||
throw new IllegalStateException("Could not obtain imported race");
|
||||
}
|
||||
|
||||
final WindImportResult windImportResult = new AbstractWindImporter.WindImportResult();
|
||||
final WindSourceWithAdditionalID windSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION,
|
||||
windSourceId);
|
||||
final Map<InputStream, String> streamsWithFilenames = new HashMap<>();
|
||||
streamsWithFilenames.put(fileItem.getInputStream(), filenameWithSuffix);
|
||||
new WindImporter().importWindToWindSourceAndTrackedRaces(service, windImportResult, windSource,
|
||||
Arrays.asList(trackedRace), streamsWithFilenames);
|
||||
raceLog.add(new RaceLogDenoteForTrackingEventImpl(denotationTimePoint, service.getServerAuthor(),
|
||||
raceLog.getCurrentPassId(), trackedRaceName, regatta.getBoatClass(), UUID.randomUUID()));
|
||||
|
||||
return new ImporterResult(event.getId(), regattaNameAndleaderboardName, leaderboardGroupName,
|
||||
trackedRace.getRaceIdentifier(), raceColumnName, fleetName,
|
||||
jsonHolderForGpsFixImport.getImportResult(), jsonHolderForSensorFixImport.getImportResult(),
|
||||
sensorFixImporterType, errors);
|
||||
raceLog.add(new RaceLogStartTrackingEventImpl(startTrackingTimePoint, author, raceLog.getCurrentPassId()));
|
||||
|
||||
return trackRace(regattaLeaderboard, raceColumn, fleet);
|
||||
} catch (Exception e) {
|
||||
throw new AllinOneImportException(e, errors);
|
||||
}
|
||||
}
|
||||
|
||||
private DynamicTrackedRace trackRace(final RegattaLeaderboard regattaLeaderboard, final RaceColumn raceColumn,
|
||||
final Fleet fleet) throws NotDenotedForRaceLogTrackingException, Exception {
|
||||
DynamicTrackedRace trackedRace;
|
||||
final RaceHandle raceHandle = adapter.startTracking(service, regattaLeaderboard, raceColumn, fleet,
|
||||
/* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ true);
|
||||
// wait for the RaceDefinition to be created
|
||||
raceHandle.getRace();
|
||||
trackedRace = WaitForTrackedRaceUtil.waitForTrackedRace(raceColumn, fleet, 10);
|
||||
if (trackedRace == null) {
|
||||
throw new IllegalStateException("Could not obtain imported race");
|
||||
}
|
||||
return trackedRace;
|
||||
}
|
||||
|
||||
private void ensureSuccessfulImport(ImportResultDTO result, String errorMessage) throws AllinOneImportException {
|
||||
if (!result.getErrorList().isEmpty()) {
|
||||
throw new AllinOneImportException(errorMessage, result.getErrorList());
|
||||
@@ -289,7 +564,7 @@ public class ExpeditionAllInOneImporter {
|
||||
|
||||
private void ensureBoatClassDetermination(Regatta regatta) throws AllinOneImportException {
|
||||
if (regatta.getBoatClass() == null) {
|
||||
throw new AllinOneImportException(ERROR_MESSAGE_BOAT_CLASS_DETERMINATION_FAILED);
|
||||
throw new AllinOneImportException(serverStringMessages.get(uiLocale, "allInOneErrorBoatClassDeterminationFailed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -50,8 +50,12 @@ class ImportResultDTO {
|
||||
private final String filename;
|
||||
private final String requestedImporter;
|
||||
|
||||
ErrorImportDTO(String name, String message) {
|
||||
this(null, name, message, null, null);
|
||||
ErrorImportDTO(String message) {
|
||||
this(null, null, message, null, null);
|
||||
}
|
||||
|
||||
ErrorImportDTO(String classname, String message) {
|
||||
this(null, classname, message, null, null);
|
||||
}
|
||||
|
||||
ErrorImportDTO(String exUUID, String name, String message, String filename, String requestedImporter) {
|
||||
|
||||
+15
@@ -8,6 +8,7 @@ import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ImportResultDTO.ErrorImportDTO;
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ImportResultDTO.TrackImportDTO;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
|
||||
/**
|
||||
* Utility class providing convenience methods to serialize import result objects into JSON objects.
|
||||
@@ -34,6 +35,18 @@ class ImportResultSerializer {
|
||||
static JSONArray serializeErrorList(List<ErrorImportDTO> errorList) {
|
||||
return serializeList(errorList, ImportResultSerializer::serializeError);
|
||||
}
|
||||
|
||||
public static JSONArray serializeRaceList(List<Triple<String, String, String>> raceNameRaceColumnNameFleetnameList) {
|
||||
return serializeList(raceNameRaceColumnNameFleetnameList, ImportResultSerializer::serializeRaceEntry);
|
||||
}
|
||||
|
||||
private static JSONObject serializeRaceEntry(Triple<String, String, String> entry) {
|
||||
final JSONObject json = new JSONObject();
|
||||
json.put("raceName", entry.getA());
|
||||
json.put("raceColumnName", entry.getB());
|
||||
json.put("fleetName", entry.getC());
|
||||
return json;
|
||||
}
|
||||
|
||||
private static JSONObject serializeError(ErrorImportDTO error) {
|
||||
final JSONObject json = new JSONObject();
|
||||
@@ -44,4 +57,6 @@ class ImportResultSerializer {
|
||||
json.put("message", error.getMessage());
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<link href='../../../../sailing-fontface-1.0.cache.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
|
||||
<title>SAP Sailing Analytics Webservices API Version 1.0</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
|
||||
<h2>URL: /api/v1/boats</h2>
|
||||
|
||||
<b>Description:</b>
|
||||
<p>Gets information about a single boat, given it's id</p>
|
||||
<br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webservice Type:</td>
|
||||
<td>REST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Output format:</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mandatory parameters:</td>
|
||||
<td>boatId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional parameters:</td>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Request method:</td>
|
||||
<td>GET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example non existing boat:</td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/boats/invalidId">https://www.sapsailing.com/sailingserver/api/v1/boats/invalidId</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example existing boat (please note that the # of the id MUST be url encoded):</td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/boats/9ee75a6e-60c2-4a69-993c-b38f45be29ad%231">https://www.sapsailing.com/sailingserver/api/v1/boats/9ee75a6e-60c2-4a69-993c-b38f45be29ad%231</a></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>Example answer</td>
|
||||
<td><pre>
|
||||
<code>{"idtype":"java.lang.String","id":"9ee75a6e-60c2-4a69-993c-b38f45be29ad#1","name":"Boot 1","sailId":"1","color":"#000000","boatClass":{"name":"J\/70","typicallyStartsUpwind":true}} </code>
|
||||
</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -244,6 +244,11 @@ name to make sure you get to the event you're interested in.
|
||||
<td><a href="raceDetailsGetDoc.html">/api/v1/trackedRaces/raceDetails</a></td>
|
||||
<td>Gets more information about a specified RegattaAndRace identifier</td>
|
||||
</tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="boats.html">/api/v1/boats</a></td>
|
||||
<td>Obtains information about a single boat</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
|
||||
+47
-3
@@ -39,8 +39,24 @@
|
||||
<td>Request method:</td>
|
||||
<td>POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Payload:</td>
|
||||
<td>
|
||||
<pre><code>
|
||||
either of:
|
||||
*competitorId ID of the competitor to map the device to.
|
||||
*boatId ID of the boat to map the device to.
|
||||
*markId ID of the boat to map the device to.
|
||||
deviceType: Either android or ios. Needed for selecting correct push notification service.
|
||||
deviceUuid: UUID generated by the app on first run. Not an identifier that globally identifies the piece of hardware such UDID under iOS, IMEI, or other.
|
||||
pushDeviceId: On Android the GCM regId, on iOS the APNS push token. This is needed for sending push notifications to the device.
|
||||
fromMillis: Milliseconds timestamp from which to map the device to the competitor.
|
||||
</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Example:</td>
|
||||
<td>Example comptitor tracking:</td>
|
||||
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start</a>
|
||||
<pre><code>
|
||||
{
|
||||
@@ -50,9 +66,37 @@
|
||||
"pushDeviceId" : "0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78",
|
||||
"fromMillis" : 1415624564718
|
||||
}
|
||||
</code></pre>
|
||||
</td>
|
||||
</code></pre>
|
||||
</td>
|
||||
<tr>
|
||||
<td>Example boat tracking:</td>
|
||||
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start</a>
|
||||
<pre><code>
|
||||
{
|
||||
"boatId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
|
||||
"deviceType" : "ios",
|
||||
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
|
||||
"pushDeviceId" : "0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78",
|
||||
"fromMillis" : 1415624564718
|
||||
}
|
||||
</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example mark tracking:</td>
|
||||
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start</a>
|
||||
<pre><code>
|
||||
{
|
||||
"markId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
|
||||
"deviceType" : "ios",
|
||||
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
|
||||
"pushDeviceId" : "0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78",
|
||||
"fromMillis" : 1415624564718
|
||||
}
|
||||
</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
|
||||
+25
-1
@@ -40,7 +40,7 @@
|
||||
<td>POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example:</td>
|
||||
<td>Example competitor:</td>
|
||||
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end</a>
|
||||
<pre><code>
|
||||
{
|
||||
@@ -51,6 +51,30 @@
|
||||
</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example mark:</td>
|
||||
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end</a>
|
||||
<pre><code>
|
||||
{
|
||||
"markId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
|
||||
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
|
||||
"toMillis" : 1415624564719
|
||||
}
|
||||
</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example boat:</td>
|
||||
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end</a>
|
||||
<pre><code>
|
||||
{
|
||||
"boatId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
|
||||
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
|
||||
"toMillis" : 1415624564719
|
||||
}
|
||||
</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
Any previously imported expedition tracks may be dropped and reimported to gain access to the newly introduced measures.</li>
|
||||
<li>The settings dialog of the competitor charts in <tt>RaceBoard.html</tt> now provides the measures in alphabetical order (based on the current locale).
|
||||
Expedition specific measures - if available - are shown at the end of the list.</li>
|
||||
<li>The all in one expedition import has been extended to be able to import new competitor tracks or new races to an existing regatta.
|
||||
Previously it was only possible to import a new event structure.</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="articleSubheadline">March 2018</h2>
|
||||
|
||||
+18
-1
@@ -144,6 +144,23 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionName":"SailingServerGateway",
|
||||
"folders":[
|
||||
{
|
||||
"startingFolderPath":"java/com.sap.sailing.server.gateway/resources/stringmessages",
|
||||
"sourceFilters":[
|
||||
"**/StringMessages*.properties"
|
||||
],
|
||||
"excludedSourceFilters":[
|
||||
"**/*_zh.properties"
|
||||
],
|
||||
"xlfFolderPath":"./XLF",
|
||||
"targetFolderPath":".",
|
||||
"pseudoLocFolderPath":"."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionName":"SailingGwtUi",
|
||||
"folders":[
|
||||
@@ -169,4 +186,4 @@
|
||||
"oneQFolderPath":"/1Q/[fullPath]/[targetFile]",
|
||||
"targetFileNamingConvention":"[filename]_[langCode].[extension]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,14 @@ While in "manual" mode these can all be imported separately, into an event/regat
|
||||
|
||||
In the AdminConsole go to "Tracked races / Wind" and scroll down to the "Import full Expedition data" compartment. Choose the log file you'd like to import. It can be the ``.csv`` file or a compressed version of it, e.g., ``something.zip`` containing one or more ``.csv`` files or ``something.csv.gz`` in GZIP compression format.
|
||||
|
||||
Choose the boat class to which the log pertains and press the "Upload" button. Usually, web browsers display the upload progress in the status bar at the bottom of the browser window. Once the upload has finished, the server will take a few minutes to think about what you have uploaded. It will create an event named after the name of the log file with the time stamp of the upload appended, e.g., ``2017Dec15_2018-01-22T13:39:48.411``, also a leaderboard group of that name, a "regatta", a regatta leaderboard and a "race" within that regatta, representing the session to which the log belongs. Most of these names can be edited afterwards. It's just intended to give you a quick start.
|
||||
There are 3 different modes for the upload you can choose of:
|
||||
* Create New Event: You need to choose this mode for your first session or whenever you need an empty setup with only the new data. You need to choose the boat class to which the log pertains. During import, this mode will create an event named after the name of the log file with the time stamp of the upload appended, e.g., ``2017Dec15_2018-01-22T13:39:48.411``, also a leaderboard group of that name, a "regatta", a regatta leaderboard and a "race" within that regatta, representing the session to which the log belongs. Most of these names can be edited afterwards. It's just intended to give you a quick start.
|
||||
* Add a new Competitor: You need to choose this mode in case you want to add the log of another competitor to your already imported tracks. In this case you need to select the regatta you intend to import the new track to. It is expected that the track files for several competitors cover approximately the same time range. The start and end times for generated races are automatically determined for the first uploaded log file. If you need to extend that time range afterwards, you can do that under "Connectors > Smartphone Tracking" by clicking the "Set tracking times" button for a race.
|
||||
* Create a new race: You need to choose this mode to import a new distinct session (e.g. new day of trainig) into an existing regatta. In this case you need to select the regatta you intend to import the new track to. The name of the generated leaderboard column and race is determined from the uploaded file name. If the same name is already used in the given regatta, a unique timestamp is appended to ensure uniqueness of race names.
|
||||
|
||||
A popup should appear after a few minutes that lets you add or choose a competitor to which the data shall be associated. Either pick an existing competitor from the pool on the right by selecting one and pressing the little angular bracket pointing left ("<") or click the "Add" button at the top of the dialog to create a new competitor which is then added automatically. Press the "Save" button.
|
||||
When uploading an expedition file using any of the described cases, a spinning animation will appear next to the upload button which indicates that the upload is running. This may take a while (some minutes) to finish depending on the size of the uploaded file.
|
||||
|
||||
A popup should appear after a few minutes that lets you add or choose a competitor to which the data shall be associated. Either pick an existing competitor from the pool on the right by selecting one and pressing the little angular bracket pointing left ("<") or click the "Add" button at the top of the dialog to create a new competitor which is then added automatically. After adding any participating competitor, select exactly one entry in the left table to specify the competitor to whom the just uploaded log file will be associated. Press the "Save" button to accept the addition of competitors and mapping of the log file to the single selected one.
|
||||
|
||||
After another short while another little popup dialog should be shown that offers two links: one to the overview page of the event just created, the other one navigating straight to the "race" viewer for the data just imported. As it is possible that the session recorded does not have a valid "race" start/finish date and no course defined, the session may not be listed in the event's "Races" tab under the "List Format." In this case choose "Competition Format" and your session should show up as a greyish "Planned" icon that you may click to enter the viewer (RaceBoard.html). This is where the race link from the popup will take you straight away.
|
||||
|
||||
@@ -28,9 +33,7 @@ The general procedure for linking a video to a race is explained [[here|wiki/how
|
||||
|
||||
### Local Video (no Upload Required)
|
||||
|
||||
First of all, you'll need a web server running locally which allows you to make a directory on your local hard disk containing your video files accessible through HTTP. [[Here|wiki/howto/setup/webserver/chrome-webserver]] is a description of how to set this up, using a little web server that runs integrated in your Chrome browser.
|
||||
|
||||
More specifically, [[this section|https://wiki.sapsailing.com/wiki/howto/setup/webserver/chrome-webserver#setup-locally-hosted-360-videos_adding-a-360-video]] then explains how to link a panoramic 360° video, choosing the correct file type.
|
||||
First of all, you'll need a web server running locally which allows you to make a directory on your local hard disk containing your video files accessible through HTTP. [[Here|wiki/howto/setup/webserver/nginx-webserver]] is a description of how to set this up, using a little web server that runs on all platforms.
|
||||
|
||||
### Uploading through AdminConsole
|
||||
|
||||
|
||||
Reference in New Issue
Block a user