Skip to content

Commit

Permalink
XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to …
Browse files Browse the repository at this point in the history
…a Live Data source

  * Improve work after Manuel's review
  * Provide some helpers to factorize code and provide better SoC
  * Use a dedicated StaticList displayer

Co-Authored-By: Manuel Leduc <[email protected]>
  • Loading branch information
surli and manuelleduc committed Mar 13, 2024
1 parent ef4f262 commit 6dc15b1
Show file tree
Hide file tree
Showing 12 changed files with 665 additions and 558 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.xwiki.notifications.filters.internal.livedata;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.inject.Inject;
import javax.inject.Provider;

import org.xwiki.livedata.LiveDataEntryStore;
import org.xwiki.livedata.LiveDataException;
import org.xwiki.livedata.LiveDataQuery;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.notifications.NotificationFormat;
import org.xwiki.notifications.filters.internal.DefaultNotificationFilterPreference;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;

import com.xpn.xwiki.XWikiContext;

/**
* Abstract class providing helpers for both
* {@link org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataEntryStore} and
* {@link org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataEntryStore}.
*
* @version $Id$
* @since 16.2.0RC1
*/
public abstract class AbstractNotificationFilterLiveDataEntryStore implements LiveDataEntryStore
{
private static final String TARGET_SOURCE_PARAMETER = "target";
private static final String WIKI_SOURCE_PARAMETER = "wiki";
private static final String UNAUTHORIZED_EXCEPTION_MSG = "You don't have rights to access those information.";

@Inject
protected NotificationFilterLiveDataTranslationHelper translationHelper;

@Inject
protected EntityReferenceResolver<String> entityReferenceResolver;

@Inject
protected EntityReferenceSerializer<String> entityReferenceSerializer;

@Inject
private Provider<XWikiContext> contextProvider;

@Inject
private ContextualAuthorizationManager contextualAuthorizationManager;

protected static final class TargetInformation
{
public boolean isWikiTarget;
public EntityReference ownerReference;

}

protected Map<String, Object> getStaticListInfo(List<String> items)
{
return Map.of(
"extraClass", "list-unstyled",
"items", items
);
}

protected Map<String, Object> displayNotificationFormats(Collection<NotificationFormat> notificationFormats)
{
List<String> items = notificationFormats
.stream()
.sorted(Comparator.comparing(NotificationFormat::name))
.map(notificationFormat -> this.translationHelper.getFormatTranslation(notificationFormat))
.toList();

return getStaticListInfo(items);
}

protected TargetInformation getTargetInformation(LiveDataQuery query) throws LiveDataException
{
Map<String, Object> sourceParameters = query.getSource().getParameters();
if (!sourceParameters.containsKey(TARGET_SOURCE_PARAMETER)) {
throw new LiveDataException("The target source parameter is mandatory.");
}
String target = String.valueOf(sourceParameters.get(TARGET_SOURCE_PARAMETER));
TargetInformation result = new TargetInformation();
if (WIKI_SOURCE_PARAMETER.equals(target)) {
result.isWikiTarget = true;
result.ownerReference =
this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER)),
EntityType.WIKI);
} else {
result.isWikiTarget = false;
result.ownerReference = this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(target)),
EntityType.DOCUMENT);
}
if (!this.contextualAuthorizationManager.hasAccess(Right.ADMIN)
&& !result.ownerReference.equals(getCurrentUserReference())) {
throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG);
}
return result;
}

protected WikiReference getCurrentWikiReference()
{
XWikiContext context = this.contextProvider.get();
return context.getWikiReference();
}

private DocumentReference getCurrentUserReference()
{
XWikiContext context = this.contextProvider.get();
return context.getUserReference();
}

protected void checkAccessFilterPreference(DefaultNotificationFilterPreference filterPreference)
throws LiveDataException
{
if (!this.contextualAuthorizationManager.hasAccess(Right.ADMIN)
&& !Objects.equals(filterPreference.getOwner(),
this.entityReferenceSerializer.serialize(getCurrentUserReference()))) {
throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import javax.inject.Inject;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.eventstream.EventStreamException;
import org.xwiki.eventstream.RecordableEventDescriptor;
Expand All @@ -35,12 +36,13 @@
import org.xwiki.notifications.NotificationFormat;
import org.xwiki.notifications.filters.NotificationFilterType;
import org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataConfigurationProvider;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;

/**
* Helper for getting various translations for live data custom sources.
*
* @version $Id$
* @since 16.2.ORC1
* @since 16.2.0RC1
*/
@Component(roles = NotificationFilterLiveDataTranslationHelper.class)
@Singleton
Expand All @@ -52,6 +54,12 @@ public class NotificationFilterLiveDataTranslationHelper
@Inject
private RecordableEventDescriptorManager recordableEventDescriptorManager;

@Inject
private WikiDescriptorManager wikiDescriptorManager;

@Inject
private Logger logger;

private String getTranslationWithFallback(String translationKey)
{
String translationPlain = this.contextualLocalizationManager.getTranslationPlain(translationKey);
Expand Down Expand Up @@ -104,15 +112,16 @@ public String getAllEventTypesTranslation()
* @return the plain text event type description translation
* @throws LiveDataException if the event type descriptor cannot be found
*/
public String getEventTypeTranslation(String eventType) throws LiveDataException
public String getEventTypeTranslation(String eventType)
{
try {
RecordableEventDescriptor descriptor =
this.recordableEventDescriptorManager.getDescriptorForEventType(eventType, true);
return getTranslationWithFallback(descriptor.getDescription());
} catch (EventStreamException e) {
throw new LiveDataException(
String.format("Error while getting description for event type [%s]", eventType), e);
this.logger.error("Error while getting description for event type [{}] falling back on event name",
eventType, e);
return eventType;
}
}

Expand All @@ -127,16 +136,16 @@ public String getFormatTranslation(NotificationFormat format)

/**
* Get event type information from all descriptor to populate a select.
* @param allFarm {@code true} if the event type should be retrieved for the whole farm.
* @return a list of maps containing two information: {@code value} holding the event type, and {@code label}
* holding a translation of the description
* @throws LiveDataException in case of problem to load the descriptors
*/
public List<Map<String, String>> getAllEventTypesOptions(boolean allFarm) throws LiveDataException
public List<Map<String, String>> getAllEventTypesOptions() throws LiveDataException
{
try {
boolean isMainWiki = this.wikiDescriptorManager.isMainWiki(this.wikiDescriptorManager.getCurrentWikiId());
List<RecordableEventDescriptor> recordableEventDescriptors =
this.recordableEventDescriptorManager.getRecordableEventDescriptors(allFarm);
this.recordableEventDescriptorManager.getRecordableEventDescriptors(isMainWiki);
return recordableEventDescriptors.stream().map(descriptor -> Map.of(
"value", descriptor.getEventType(),
"label", getTranslationWithFallback(descriptor.getDescription())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.inject.Inject;
Expand Down Expand Up @@ -69,8 +68,11 @@ public class NotificationCustomFiltersLiveDataConfigurationProvider implements P
static final String DOC_HAS_DELETE_FIELD = "doc_hasdelete";
private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.custom.table.";
private static final String DELETE = "delete";
// FIXME: We should define those constants in LiveData module
private static final String STRING_TYPE = "String";
private static final String HTML_DISPLAYER = "html";
private static final String BOOLEAN = "boolean";
private static final String STATIC_LIST_DISPLAYER = "staticList";
private static final String VALUE_KEY = "value";
private static final String LABEL_KEY = "label";

Expand Down Expand Up @@ -148,7 +150,8 @@ public LiveDataConfiguration get()
deleteAction.setId(DELETE);
deleteAction.setAllowProperty(DOC_HAS_DELETE_FIELD);
try {
deleteAction.setIcon(this.iconManager.getMetaData(DELETE));
// FIXME: we should map delete action icon to the cross..
deleteAction.setIcon(this.iconManager.getMetaData("cross"));
} catch (IconException e) {
this.logger.error("Error while getting icon for the remove action", e);
}
Expand All @@ -175,6 +178,7 @@ private LiveDataPropertyDescriptor getDisplayDescriptor()
descriptor.setId(DISPLAY_FIELD);
descriptor.setType(STRING_TYPE);
descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER));
// This is not visible because we don't want to keep that property and it's aiming at being removed.
descriptor.setVisible(false);
descriptor.setEditable(false);
descriptor.setSortable(false);
Expand All @@ -187,9 +191,9 @@ private LiveDataPropertyDescriptor.FilterDescriptor createFilterList(List<Map<St
{
LiveDataPropertyDescriptor.FilterDescriptor filterList =
new LiveDataPropertyDescriptor.FilterDescriptor("list");
filterList.addOperator("empty", null);
filterList.setParameter("options", options);
String equalsOperator = "equals";
// We do not want any other operator than equals
filterList.addOperator(equalsOperator, null);
filterList.setDefaultOperator(equalsOperator);
return filterList;
Expand All @@ -206,7 +210,7 @@ private LiveDataPropertyDescriptor getScopeDescriptor()
.map(item -> Map.of(
VALUE_KEY, item.name(),
LABEL_KEY, this.translationHelper.getScopeTranslation(item)
)).collect(Collectors.toList())));
)).toList()));
descriptor.setVisible(true);
descriptor.setEditable(false);
descriptor.setSortable(true);
Expand Down Expand Up @@ -240,7 +244,7 @@ private LiveDataPropertyDescriptor getFilterTypeDescriptor()
.map(item -> Map.of(
VALUE_KEY, item.name(),
LABEL_KEY, this.translationHelper.getFilterTypeTranslation(item)
)).collect(Collectors.toList())));
)).toList()));
descriptor.setVisible(true);
descriptor.setEditable(false);
descriptor.setSortable(true);
Expand All @@ -255,12 +259,12 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor()
descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NOTIFICATION_FORMATS_FIELD));
descriptor.setId(NOTIFICATION_FORMATS_FIELD);
descriptor.setType(STRING_TYPE);
descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER));
descriptor.setFilter(createFilterList(Stream.of(NotificationFormat.values()).map(item ->
Map.of(
VALUE_KEY, item.name(),
LABEL_KEY, this.translationHelper.getFormatTranslation(item)
)).collect(Collectors.toList())));
)).toList()));
descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(STATIC_LIST_DISPLAYER));
descriptor.setVisible(true);
descriptor.setEditable(false);
descriptor.setSortable(true);
Expand All @@ -275,14 +279,13 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor()
descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + EVENT_TYPES_FIELD));
descriptor.setId(EVENT_TYPES_FIELD);
descriptor.setType(STRING_TYPE);
descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER));
descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(STATIC_LIST_DISPLAYER));
List<Map<String, String>> options = new ArrayList<>();
options.add(Map.of(
VALUE_KEY, ALL_EVENTS_OPTION_VALUE,
LABEL_KEY, this.translationHelper.getAllEventTypesTranslation()));
try {
// FIXME: all farm shouldn't always be true
options.addAll(this.translationHelper.getAllEventTypesOptions(true));
options.addAll(this.translationHelper.getAllEventTypesOptions());
} catch (LiveDataException e) {
this.logger.error("Cannot provide event filter options", e);
}
Expand All @@ -300,10 +303,8 @@ private LiveDataPropertyDescriptor getIsEnabledDescriptor()
LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor();
descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + IS_ENABLED_FIELD));
descriptor.setId(IS_ENABLED_FIELD);
descriptor.setType("Boolean");
LiveDataPropertyDescriptor.FilterDescriptor filterBoolean =
new LiveDataPropertyDescriptor.FilterDescriptor("boolean");
descriptor.setFilter(filterBoolean);
descriptor.setType(BOOLEAN);
descriptor.setFilter(new LiveDataPropertyDescriptor.FilterDescriptor(BOOLEAN));
descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("toggle"));
descriptor.setVisible(true);
descriptor.setEditable(false);
Expand Down
Loading

0 comments on commit 6dc15b1

Please sign in to comment.