Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c1fe762
add didOptOutOfMarketingEmails flag to User creation
sohaib-sap Nov 28, 2025
2841b71
wip: add marketing email Opt out feature in UI
sohaib-sap Nov 28, 2025
ebd48be
remove marketing email checkbox from UserDetails screen
sohaib-sap Dec 11, 2025
a722f6c
merge main
sohaib-sap Jan 16, 2026
cc00c99
Revert "merge main"
sohaib-sap Jan 16, 2026
961d8a9
feat: updateUserProperties' email parameter can be null
sohaib-sap Jan 20, 2026
1373405
Merge branch 'main' into bug6187
sohaib-sap Jan 20, 2026
27d4abc
earlier merge did not include file
sohaib-sap Jan 20, 2026
6ad5f29
rename: marketing emails to feature and community updates
sohaib-sap Jan 20, 2026
a2621fa
wip: miscellaneous box added under favorites/notifications
sohaib-sap Jan 20, 2026
6d9b48e
checkbox tile now works to set email subscription flag
sohaib-sap Jan 29, 2026
a5d48b6
ui: added loading overlay for checkbox
sohaib-sap Jan 29, 2026
d38bdcf
Merge branch 'main' into bug6187
sohaib-sap Feb 2, 2026
b537d4a
add "Did opt out of Feature and Community Emails" filter on data mining
sohaib-sap Feb 5, 2026
c77f7b3
fix: flag missing from user record
sohaib-sap Feb 8, 2026
ca0dc8b
Merge branch 'main' into bug6187
sohaib-sap Feb 9, 2026
9899f9e
Merge branch 'main' into bug6187
sohaib-sap Mar 5, 2026
b9baee6
fix earlier botched commit
sohaib-sap Mar 5, 2026
67e5d8b
complete previous commit
sohaib-sap Mar 6, 2026
d6f88f2
Merge branch 'main' into bug6187
sohaib-sap Mar 6, 2026
88df14b
Undo changes to launch config
sohaib-sap Mar 6, 2026
1f137d1
bug6187: made default constructor of BooleanResult @Deprecated, remov…
axeluhl Mar 6, 2026
ac0b318
bug6187: fixed German message translation
axeluhl Mar 6, 2026
6f6f57e
bug6187: added comment about null meaning no change
axeluhl Mar 6, 2026
3d6c885
bug6187: no explicit DB "migration" for new boolean opt-out flag
axeluhl Mar 6, 2026
1d6d92b
Merge remote-tracking branch 'github/bug6187' into bug6187
axeluhl Mar 6, 2026
e695c7e
undo changes to .launch file
sohaib-sap Mar 6, 2026
aa5c413
bug6187: UserImpl should use boolean, not Boolean, for constructor args
axeluhl Mar 6, 2026
c872a2f
Merge remote-tracking branch 'github/bug6187' into bug6187
axeluhl Mar 6, 2026
96f92f7
bug6187: "drive-by" change: UpdateUserPropertiesOperation fields shou…
axeluhl Mar 6, 2026
8fa60eb
bug6187: added comment for "false" argument
axeluhl Mar 6, 2026
05a73d5
revert changes to .launch files
sohaib-sap Mar 6, 2026
893a000
fix: removed "subscription", concatenation from email error message
sohaib-sap Mar 6, 2026
8a7e6d3
doc: updateUserProperties(...) passing null = no update for that param
sohaib-sap Mar 6, 2026
e546557
wip: decoupling of checkbox tile from suggested multi selection
sohaib-sap Mar 20, 2026
c6f0b8f
code cleanup, renaming for clarity
sohaib-sap Mar 23, 2026
23f1cd6
SuggestedMultiSelection decoupled from "notification" or checkboxes
sohaib-sap Mar 23, 2026
0f673f3
updateUserProperty documentation, email opt out flag in user mgmt table
sohaib-sap Mar 24, 2026
f1cabb1
Merge branch 'bug6187' of github.com:SAP/sailing-analytics into bug6187
sohaib-sap Mar 24, 2026
88b390c
Merge branch 'main' into bug6187
sohaib-sap Mar 24, 2026
fb0e2b9
DE string messages correction + fix merge error in UpdateUserProp...
sohaib-sap Mar 24, 2026
d67a76c
Merge branch 'main' into bug6187
sohaib-sap Mar 30, 2026
50cb4e2
fix: minor artifact from last merge corrected
sohaib-sap Mar 30, 2026
cb50f0d
fix: minor artifact from previous merge corrected
sohaib-sap Mar 30, 2026
3d2c48e
ui, bugfix: user management table > opt out column changes
sohaib-sap Mar 31, 2026
405aeb3
Merge branch 'main' into bug6187
axeluhl Apr 2, 2026
423a7f3
Merge branch 'main' into bug6187
axeluhl Apr 6, 2026
a594e86
bug6187: release notes
axeluhl Apr 6, 2026
a99ebc0
Merge branch 'main' into bug6187
axeluhl Apr 7, 2026
81b3fc9
Merge branch 'main' into bug6187
axeluhl Apr 7, 2026
91e85c4
bug6187: minor code simplification for initial setting of opt-out che…
axeluhl Apr 7, 2026
de094b0
Merge remote-tracking branch 'github/bug6187' into bug6187
axeluhl Apr 8, 2026
7e5331a
bug6187: fixed typos and translation issues in StringMessages, added …
axeluhl Apr 9, 2026
15d6fbb
changes made per Axel Claude Code review
sohaib-sap Apr 13, 2026
265d9c3
Merge branch 'main' into bug6187
sohaib-sap Apr 13, 2026
05cb006
wip: MiscPresenter
sohaib-sap Apr 13, 2026
c37fa0e
bug6187: removed redundant "public" modifiers from interface methods
axeluhl Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion db/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ public class FavoritesResult implements Result {

private FavoriteBoatClassesDTO favoriteBoatClasses;
private FavoriteCompetitorsDTO favoriteCompetitors;
private boolean isSubscribedToFeatureAndCommunityUpdates;

protected FavoritesResult() {}

public FavoritesResult(FavoriteBoatClassesDTO favoriteBoatClasses, FavoriteCompetitorsDTO favoriteCompetitors) {
public FavoritesResult(FavoriteBoatClassesDTO favoriteBoatClasses, FavoriteCompetitorsDTO favoriteCompetitors, boolean isSubscribedToFeatureAndCommunityUpdates) {
this.favoriteBoatClasses = favoriteBoatClasses;
this.favoriteCompetitors = favoriteCompetitors;
}
Expand All @@ -22,4 +23,8 @@ public FavoriteCompetitorsDTO getFavoriteCompetitors() {
return favoriteCompetitors;
}

public boolean getIsSubscribedToFeatureAndCommunityUpdates() {
return isSubscribedToFeatureAndCommunityUpdates;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ public class GetFavoritesAction implements SailingAction<FavoritesResult> {
@Override
@GwtIncompatible
public FavoritesResult execute(SailingDispatchContext ctx) throws DispatchException {
return new FavoritesResult(getFavoriteBoatClasses(ctx), getFavoriteCompetitors(ctx));
return new FavoritesResult(getFavoriteBoatClasses(ctx), getFavoriteCompetitors(ctx),
getDidOptOutOfFeatureAndCommunityEmails(ctx));
}

private boolean getDidOptOutOfFeatureAndCommunityEmails(SailingDispatchContext ctx) {
return ctx.getSecurityService().getCurrentUser().getDidOptOutOfFeatureAndCommunityEmails();
}

@GwtIncompatible
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.sap.sailing.gwt.home.communication.user.profile;

import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.gwt.home.communication.SailingAction;
import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
import com.sap.sse.gwt.dispatch.shared.commands.HasWriteAction;
import com.sap.sse.gwt.dispatch.shared.commands.VoidResult;
import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException;
import com.sap.sse.security.shared.UserManagementException;

public class SaveMiscEmailPreferences implements SailingAction<VoidResult>, HasWriteAction {
private Boolean subscribeToFeatureAndCommunityUpdates;

protected SaveMiscEmailPreferences() {
}

public SaveMiscEmailPreferences(final Boolean subscribeToFeatureAndCommunityUpdates) {
this.subscribeToFeatureAndCommunityUpdates = subscribeToFeatureAndCommunityUpdates;
}

@Override
@GwtIncompatible
public VoidResult execute(SailingDispatchContext ctx) throws DispatchException {
try {
final String username = ctx.getSecurityService().getCurrentUser().getName();
ctx.getSecurityService().updateUserProperties(username, null, null, null,
!subscribeToFeatureAndCommunityUpdates);
} catch (UserManagementException e) {
throw new DispatchException(e.getMessage());
}
return new VoidResult();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ <h5 class="articleSubheadline">April 2026</h5>
This comes with general improvements in table selection/de-selection handling.</li>
<li>Adding the "Race Rank" column to the leaderboard now consistently adds it after the selection
checkbox column and displays it immediately when the settings dialog is confirmed.</li>
<li>Users can now opt out of e-mail communication regarding features and community
information. This will be honored when sending out such general information about
the Sailing Analytics solution.</li>
</ul>
<h5 class="articleSubheadline">March 2026</h5>
<ul class="bulletList">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.container {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5em;
padding: 0.333333333333333em;
}

.checkbox {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2em;
height: 2em;
}

.checkbox input {
width: 1.333333333333333em;
height: 2em;
}

.label {
flex: auto;
font-weight: 600;
padding-right: 2.666666666666667em;
line-height: 2em;
min-width: 0;
text-align: left;
}

.loadingOverlay {
position: absolute;
inset: 0;
pointer-events: all;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.3);
z-index: 10;
}

.spinner {
width: 60%;
aspect-ratio: 1/1;
border: 2px solid #ccc;
border-top-color: #1976d2;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

@keyframes
spin {
from {
transform:rotate(0deg);
} to {
transform: rotate(360deg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.sap.sailing.gwt.home.shared.partials.checkboxtile;

import java.util.function.BiConsumer;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.dispatch.shared.commands.VoidResult;

/**
* @param onToggle
* if null, toggle will be disabled
*/
public final class CheckBoxTile extends Composite implements HasValue<Boolean> {
private static CheckBoxTileUiBinder uiBinder = GWT.create(CheckBoxTileUiBinder.class);

interface CheckBoxTileUiBinder extends UiBinder<Widget, CheckBoxTile> {
}

@UiField
CheckBoxTileResources res;
@UiField
Label labelUi;
@UiField
CheckBox toggleButtonUi;
private DivElement loadingOverlay;

public CheckBoxTile(final String label, final boolean initialState,
final BiConsumer<Boolean, AsyncCallback<VoidResult>> onToggle) {
super();
CheckBoxTileResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
labelUi.setText(label);
initToggleButtonUi(initialState, onToggle);
}

private void initToggleButtonUi(final boolean initialState,
final BiConsumer<Boolean, AsyncCallback<VoidResult>> onToggle) {
toggleButtonUi.setValue(initialState);
if (onToggle == null) {
toggleButtonUi.setEnabled(false);
}
toggleButtonUi.getElement().getStyle().setProperty("position", "relative");
final ValueChangeHandler<Boolean> loadingHandler = value -> {
final Boolean newlyToggledValue = value.getValue();
overlayLoadingSpinner();
final AsyncCallback<VoidResult> callback = new AsyncCallback<VoidResult>() {
@Override
public void onFailure(Throwable caught) {
// undo failed toggle, false arg enforces silence of change handlers on this call
toggleButtonUi.setValue(!newlyToggledValue, false);
hideLoadingSpinner();
}

@Override
public void onSuccess(VoidResult result) {
hideLoadingSpinner();
}
};
onToggle.accept(newlyToggledValue, callback);
};
toggleButtonUi.addValueChangeHandler(loadingHandler);
}

private void overlayLoadingSpinner() {
toggleButtonUi.setEnabled(false);
if (loadingOverlay == null) {
initLoadingOverlay();
}
loadingOverlay.getStyle().setProperty("display", "flex");
}

private void initLoadingOverlay() {
// add elements
loadingOverlay = Document.get().createDivElement();
loadingOverlay.addClassName(res.css().loadingOverlay());
final DivElement spinner = Document.get().createDivElement();
spinner.addClassName(res.css().spinner());
// add to canvas
loadingOverlay.appendChild(spinner);
toggleButtonUi.getElement().appendChild(loadingOverlay);
}

private void hideLoadingSpinner() {
if (loadingOverlay != null) {
loadingOverlay.getStyle().setProperty("display", "none");
toggleButtonUi.setEnabled(true);
}
}

@Override
public void setValue(Boolean b) {
toggleButtonUi.setValue(b);
}

@Override
public Boolean getValue() {
return toggleButtonUi.getValue();
}

@Override
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Boolean> handler) {
return toggleButtonUi.addValueChangeHandler(handler);
}

@Override
public void setValue(Boolean value, boolean fireEvents) {
toggleButtonUi.setValue(value, fireEvents);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.home.shared.partials.checkboxtile.CheckBoxTileResources" />
<g:HTMLPanel addStyleNames="{res.css.container}">
<g:Label addStyleNames="{res.css.label}" ui:field="labelUi"/>
<g:CheckBox addStyleNames="{res.css.checkbox}" ui:field="toggleButtonUi" />
</g:HTMLPanel>
</ui:UiBinder>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.sap.sailing.gwt.home.shared.partials.checkboxtile;

import com.google.gwt.core.shared.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;

public interface CheckBoxTileResources extends ClientBundle {

public static final CheckBoxTileResources INSTANCE = GWT.create(CheckBoxTileResources.class);

@Source("CheckBoxTile.gss")
LocalCss css();

public interface LocalCss extends CssResource {
String container();
String label();
String checkbox();
String loadingOverlay();
String spinner();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.sap.sailing.gwt.home.shared.partials.labeledbox;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;

public class LabeledBox extends Composite {
@UiField
SpanElement headerTitleUi;
@UiField(provided = true)
Widget childUi;

private static LabeledBoxUiBinder uiBinder = GWT.create(LabeledBoxUiBinder.class);

interface LabeledBoxUiBinder extends UiBinder<Widget, LabeledBox> {
}

public LabeledBox(final String title, final Widget childUi) {
this.childUi = childUi;
initWidget(uiBinder.createAndBindUi(this));
headerTitleUi.setInnerText(title);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:with field="local_res"
type="com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionResources" />
<ui:style>

</ui:style>
<g:HTMLPanel addStyleNames="{local_res.css.suggestions}">
<div class="{local_res.css.suggestionsHeader}">
<span class="{local_res.css.suggestionsHeaderTitle}"
ui:field="headerTitleUi" />
</div>
<g:Widget ui:field="childUi" />
</g:HTMLPanel>
</ui:UiBinder>
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,18 @@ public final void clearSelection() {
selectedItemsMap.clear();
persist();
}

@Override
public Collection<T> getSelection() {
return new HashSet<>(selectedItemsMap.values());
}

@Override
public final void initSelectedItems(Collection<T> selectedItems) {
public final void initSelectedItems(Iterable<T> selectedItems) {
selectedItemsMap.clear();
for (T item : selectedItems) {
selectedItemsMap.put(getKey(item), item);
}
for (D display : displays) {
display.setSelectedItems(selectedItems);
}
}

@Override
Expand Down
Loading
Loading