From 18f2bd6efc4d757e828dbfb3c2cd249377545f21 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Tue, 5 Mar 2024 12:16:03 +0100 Subject: [PATCH 01/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source WIP --- .../pom.xml | 5 + .../NotificationFilterPreferenceStore.java | 31 ++ ...nFiltersLiveDataConfigurationProvider.java | 296 +++++++++++++ ...nFiltersLiveDataConfigurationResolver.java | 53 +++ ...NotificationFiltersLiveDataEntryStore.java | 417 ++++++++++++++++++ ...iltersLiveDataPropertyDescriptorStore.java | 54 +++ .../NotificationFiltersLiveDataSource.java | 58 +++ .../main/resources/META-INF/components.txt | 13 +- ...ficationsCustomFiltersPreferencesMacro.xml | 27 +- .../vue/src/components/DisplayerScope.vue | 59 +++ .../src/main/vue/src/main.js | 3 +- 11 files changed, 988 insertions(+), 28 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationResolver.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataPropertyDescriptorStore.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/pom.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/pom.xml index 9eac320c7f98..e4877e193f0b 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/pom.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/pom.xml @@ -45,6 +45,11 @@ xwiki-platform-oldcore ${project.version} + + org.xwiki.platform + xwiki-platform-livedata-api + ${project.version} + diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java index dfd4f380a70a..bb6b569b3583 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import javax.inject.Inject; @@ -77,6 +78,36 @@ public class NotificationFilterPreferenceStore @Inject private ObservationManager observation; + public Optional getFilterPreference(String filterPreferenceId, + WikiReference wikiReference) throws NotificationException + { + Optional result = Optional.empty(); + DefaultNotificationFilterPreference filterPreference = configureContextWrapper(wikiReference, () -> { + Query query; + try { + query = this.queryManager.createQuery( + "select nfp from DefaultNotificationFilterPreference nfp where nfp.id = :id", + Query.HQL); + query.setLimit(1); + query.bindValue("id", filterPreferenceId); + + List results = query.execute(); + if (!results.isEmpty()) { + return results.get(0); + } + } catch (QueryException e) { + throw new NotificationException( + String.format("Error while retrieving notification with id [%s]", filterPreferenceId), e); + } + return null; + }); + if (filterPreference != null) { + result = Optional.of(filterPreference); + } + + return result; + } + /** * Get the notification preference that corresponds to the given id and user. * diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java new file mode 100644 index 000000000000..ebbe781a25d0 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java @@ -0,0 +1,296 @@ +/* + * 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.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.slf4j.Logger; +import org.xwiki.component.annotation.Component; +import org.xwiki.icon.IconManager; +import org.xwiki.livedata.LiveDataConfiguration; +import org.xwiki.livedata.LiveDataEntryDescriptor; +import org.xwiki.livedata.LiveDataMeta; +import org.xwiki.livedata.LiveDataPaginationConfiguration; +import org.xwiki.livedata.LiveDataPropertyDescriptor; +import org.xwiki.localization.ContextualLocalizationManager; +import org.xwiki.notifications.filters.NotificationFilterType; + +import com.xpn.xwiki.objects.classes.LevelsClass; + +/** + * Configuration of the {@link NotificationFiltersLiveDataSource}. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationFiltersLiveDataSource.NAME) +public class NotificationFiltersLiveDataConfigurationProvider implements Provider +{ + private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.custom.table."; + static final String ID_FIELD = "filterPreferenceId"; + static final String SCOPE_FIELD = "scope"; + static final String LOCATION_FIELD = "location"; + // FIXME: Should we keep that? + static final String DISPLAY_FIELD = "display"; + static final String FILTER_TYPE_FIELD = "filterType"; + static final String EVENT_TYPES_FIELD = "eventTypes"; + static final String NOTIFICATION_FORMATS_FIELD = "notificationFormats"; + static final String IS_ENABLED_FIELD = "isEnabled"; + static final String IS_ENABLED_CHECKED_FIELD = "isEnabled_checked"; + static final String DOC_VIEWABLE_FIELD = "doc_viewable"; + static final String DOC_HAS_DELETE_FIELD = "doc_hasdelete"; + + enum Scope + { + WIKI, + SPACE, + PAGE, + USER + }; + + private static final String REMOVE = "remove"; + private static final String STRING_TYPE = "String"; + private static final String LIST_TYPE = "java.util.List"; + + @Inject + private ContextualLocalizationManager l10n; + + @Inject + private IconManager iconManager; + + @Inject + private Logger logger; + + @Override + public LiveDataConfiguration get() + { + LiveDataConfiguration input = new LiveDataConfiguration(); + LiveDataMeta meta = new LiveDataMeta(); + input.setMeta(meta); + + LiveDataPaginationConfiguration pagination = new LiveDataPaginationConfiguration(); + // FIXME: probably not for system filters? + pagination.setShowPageSizeDropdown(true); + meta.setPagination(pagination); + + LiveDataEntryDescriptor entryDescriptor = new LiveDataEntryDescriptor(); + entryDescriptor.setIdProperty(ID_FIELD); + meta.setEntryDescriptor(entryDescriptor); + + // FIXME: Handle delete action + /* + LiveDataActionDescriptor removeAction = new LiveDataActionDescriptor(); + removeAction.setName(this.l10n.getTranslationPlain("wordsNotification.settings.remove")); + removeAction.setId(REMOVE); + removeAction.setAllowProperty(DOC_HAS_DELETE_FIELD); + removeAction.setUrlProperty(REMOVE_OBJECT_URL_FIELD); + try { + removeAction.setIcon(this.iconManager.getMetaData(REMOVE)); + } catch (IconException e) { + this.logger.error("Error while getting icon for the remove action", e); + } + meta.setActions(List.of(removeAction)); + */ + + meta.setPropertyDescriptors(List.of( + getDisplayDescriptor(), + getScopeDescriptor(), + getLocationDescriptor(), + getFilterTypeDescriptor(), + getNotificationFormatsDescriptor(), + getEventTypesDescriptor(), + getIsEnabledDescriptor() + )); + + return input; + } + + private LiveDataPropertyDescriptor getDisplayDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + // FIXME: this translation should be changed + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "name")); + descriptor.setId(DISPLAY_FIELD); + descriptor.setType(STRING_TYPE); + // FIXME: we should maybe display it by default, as it's what makes custom filter displayer works... + descriptor.setVisible(false); + descriptor.setEditable(false); + descriptor.setSortable(false); + descriptor.setFilterable(false); + + return descriptor; + } + + private LiveDataPropertyDescriptor getScopeDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + // FIXME: this translation should be changed + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "scope")); + descriptor.setId(SCOPE_FIELD); + descriptor.setType(STRING_TYPE); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("scope")); + LiveDataPropertyDescriptor.FilterDescriptor filterList = + new LiveDataPropertyDescriptor.FilterDescriptor("list"); + filterList.addOperator("empty", null); + // TODO: Provide a generic component for those translations? + String translationPrefix = "notifications.filters.preferences.scopeNotificationFilter."; + filterList.setParameter("options", Stream.of(Scope.values()) + .map(item -> { + String label = l10n.getTranslationPlain(translationPrefix + item.name().toLowerCase()); + if (label == null) { + label = item.name().toLowerCase(); + } + return Map.of( + "value", item.name(), + "label", label + ); + }).collect(Collectors.toList())); + filterList.addOperator("equals", null); + descriptor.setFilter(filterList); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(true); + descriptor.setFilterable(true); + + return descriptor; + } + + private LiveDataPropertyDescriptor getLocationDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + // FIXME: this translation should be changed + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "location")); + descriptor.setId(LOCATION_FIELD); + descriptor.setType(STRING_TYPE); + // FIXME: We should provide a new custom location displayer for LD. + //descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("location")); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(true); + descriptor.setFilterable(true); + + return descriptor; + } + + private LiveDataPropertyDescriptor getFilterTypeDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "filterType")); + descriptor.setId(FILTER_TYPE_FIELD); + descriptor.setType(STRING_TYPE); + LiveDataPropertyDescriptor.FilterDescriptor filterList = + new LiveDataPropertyDescriptor.FilterDescriptor("list"); + filterList.addOperator("empty", null); + String translationPrefix = "notifications.filters.type.custom."; + filterList.setParameter("options", Stream.of(NotificationFilterType.values()) + .map(item -> { + String label = l10n.getTranslationPlain(translationPrefix + item.name().toLowerCase()); + if (label == null) { + label = item.name().toLowerCase(); + } + return Map.of( + "value", item.name(), + "label", label + ); + }).collect(Collectors.toList())); + filterList.addOperator("equals", null); + descriptor.setFilter(filterList); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(true); + descriptor.setFilterable(true); + + return descriptor; + } + + private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "notificationFormats")); + descriptor.setId(NOTIFICATION_FORMATS_FIELD); + descriptor.setType(LIST_TYPE); + // FIXME: We should provide a new custom displayer for this. + //descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("notificationFormats")); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(true); + descriptor.setFilterable(true); + + return descriptor; + } + + private LiveDataPropertyDescriptor getEventTypesDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "eventTypes")); + descriptor.setId(EVENT_TYPES_FIELD); + descriptor.setType(LIST_TYPE); + // FIXME: We should provide a new custom displayer for this. + //descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("eventTypes")); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(true); + descriptor.setFilterable(true); + + return descriptor; + } + + private LiveDataPropertyDescriptor getIsEnabledDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "isEnabled")); + descriptor.setId(IS_ENABLED_FIELD); + descriptor.setType("Boolean"); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("toggle")); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(true); + descriptor.setFilterable(true); + + return descriptor; + } + + /* + private LiveDataPropertyDescriptor getActionDescriptor() + { + LiveDataPropertyDescriptor actionDescriptor = new LiveDataPropertyDescriptor(); + actionDescriptor.setName(this.l10n.getTranslationPlain("wordsNotification.livedata.action")); + actionDescriptor.setId(ACTIONS_FIELD); + LiveDataPropertyDescriptor.DisplayerDescriptor displayer = + new LiveDataPropertyDescriptor.DisplayerDescriptor(ACTIONS_FIELD); + displayer.setParameter(ACTIONS_FIELD, List.of(REMOVE)); + actionDescriptor.setDisplayer(displayer); + actionDescriptor.setVisible(true); + actionDescriptor.setEditable(false); + actionDescriptor.setSortable(false); + actionDescriptor.setFilterable(false); + return actionDescriptor; + }*/ +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationResolver.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationResolver.java new file mode 100644 index 000000000000..da5fec521736 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationResolver.java @@ -0,0 +1,53 @@ +/* + * 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 javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataConfiguration; +import org.xwiki.livedata.LiveDataConfigurationResolver; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.internal.JSONMerge; + +/** + * Needed component for the live data configuration. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationFiltersLiveDataSource.NAME) +public class NotificationFiltersLiveDataConfigurationResolver implements + LiveDataConfigurationResolver +{ + @Inject + @Named(NotificationFiltersLiveDataSource.NAME) + private Provider notificationFiltersLiveDataConfigurationProvider; + + @Override + public LiveDataConfiguration resolve(LiveDataConfiguration input) throws LiveDataException + { + return new JSONMerge().merge(input, this.notificationFiltersLiveDataConfigurationProvider.get()); + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java new file mode 100644 index 000000000000..a850ed124c1a --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java @@ -0,0 +1,417 @@ +/* + * 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.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveData; +import org.xwiki.livedata.LiveDataEntryStore; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.LiveDataQuery; +import org.xwiki.localization.ContextualLocalizationManager; +import org.xwiki.model.EntityType; +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.NotificationException; +import org.xwiki.notifications.filters.NotificationFilter; +import org.xwiki.notifications.filters.NotificationFilterManager; +import org.xwiki.notifications.filters.NotificationFilterPreference; +import org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore; +import org.xwiki.notifications.filters.internal.scope.ScopeNotificationFilter; +import org.xwiki.query.Query; +import org.xwiki.query.QueryException; +import org.xwiki.query.QueryManager; +import org.xwiki.rendering.block.Block; +import org.xwiki.rendering.renderer.BlockRenderer; +import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; +import org.xwiki.rendering.renderer.printer.WikiPrinter; + +import com.xpn.xwiki.XWikiContext; + +/** + * Dedicated {@link LiveDataEntryStore} for the {@link NotificationFiltersLiveDataSource}. + * This component is in charge of performing the actual HQL queries to display the live data. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationFiltersLiveDataSource.NAME) +public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore +{ + private static final String STARTS_WITH_OPERATOR = "startsWith"; + private static final String CONTAINS_OPERATOR = "contains"; + private static final String EQUALS_OPERATOR = "equals"; + private static final String EMAIL_FORMAT = "email"; + private static final String ALERT_FORMAT = "alert"; + + @Inject + private NotificationFilterPreferenceStore notificationFilterPreferenceStore; + + @Inject + private Provider contextProvider; + + @Inject + private NotificationFilterManager notificationFilterManager; + + @Inject + private EntityReferenceResolver entityReferenceResolver; + + @Inject + private EntityReferenceSerializer entityReferenceSerializer; + + @Inject + @Named(ScopeNotificationFilter.FILTER_NAME) + private NotificationFilter scopeNotificationFilter; + + @Inject + @Named("html/5.0") + private BlockRenderer blockRenderer; + + @Inject + private QueryManager queryManager; + + @Inject + private ContextualLocalizationManager localizationManager; + + @Override + public Optional> get(Object entryId) throws LiveDataException + { + Optional> result = Optional.empty(); + WikiReference wikiReference = contextProvider.get().getWikiReference(); + Optional filterPreferenceOpt; + try { + filterPreferenceOpt = notificationFilterPreferenceStore + .getFilterPreference(String.valueOf(entryId), wikiReference); + if (filterPreferenceOpt.isPresent()) { + NotificationFilterPreference filterPreference = filterPreferenceOpt.get(); + result = Optional.of(getPreferenceInformation(filterPreference)); + } + } catch (NotificationException e) { + throw new LiveDataException( + String.format("Error while retrieving LiveData entry for notification filter with id [%s]", + entryId), e); + } + + return result; + } + + private Map getPreferenceInformation(NotificationFilterPreference filterPreference) + throws NotificationException, LiveDataException + { + NotificationFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); + EntityReference location = null; + switch (scope) { + case WIKI -> location = this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); + case SPACE -> location = this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); + case PAGE -> location = this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), + EntityType.DOCUMENT); + case USER -> location = this.entityReferenceResolver.resolve(filterPreference.getUser(), + EntityType.DOCUMENT); + } + return Map.of( + NotificationFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId(), + NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, filterPreference.getEventTypes(), + NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, + filterPreference.getNotificationFormats(), + NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, filterPreference.isEnabled(), + NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope), + NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, + this.entityReferenceSerializer.serialize(location), + NotificationFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, this.renderDisplay(filterPreference), + NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, + localizationManager.getTranslationPlain("notifications.filters.type.custom." + + filterPreference.getFilterType().name().toLowerCase()) + ); + } + + private Map getScopeInfo(NotificationFiltersLiveDataConfigurationProvider.Scope scope) + { + String translationPrefix = "notifications.filters.preferences.scopeNotificationFilter."; + String name = translationPrefix + scope.name().toLowerCase(); + String icon = ""; + switch (scope) { + case WIKI -> icon = "wiki"; + case SPACE -> icon = "chart-organisation"; + case PAGE -> icon = "page"; + case USER -> icon = "user"; + } + return Map.of("icon", icon, "name", this.localizationManager.getTranslationPlain(name)); + } + + private String renderDisplay(NotificationFilterPreference filterPreference) + throws NotificationException, LiveDataException + { + String result; + WikiPrinter printer = new DefaultWikiPrinter(); + // FIXME: here I hardcode the fact that the pref is for a ScopeNotificationFilter, need to check if that's + // always true + Block block = this.notificationFilterManager.displayFilter(scopeNotificationFilter, filterPreference); + try { + blockRenderer.render(block, printer); + result = printer.toString(); + } catch (Exception e) { + throw new LiveDataException("Error while rendering a block for notification filter", e); + } + return result; + } + + private NotificationFiltersLiveDataConfigurationProvider.Scope getScope(NotificationFilterPreference + filterPreference) + { + if (!StringUtils.isBlank(filterPreference.getUser())) { + return NotificationFiltersLiveDataConfigurationProvider.Scope.USER; + } else if (!StringUtils.isBlank(filterPreference.getPageOnly())) { + return NotificationFiltersLiveDataConfigurationProvider.Scope.PAGE; + } else if (!StringUtils.isBlank(filterPreference.getPage())) { + return NotificationFiltersLiveDataConfigurationProvider.Scope.SPACE; + } else { + return NotificationFiltersLiveDataConfigurationProvider.Scope.WIKI; + } + } + + private static class FiltersHQLQuery + { + String whereClause; + Map bindings = new LinkedHashMap<>(); + } + + private Optional handleFilter(List queryFilters) + { + FiltersHQLQuery result = new FiltersHQLQuery(); + List queryWhereClauses = new ArrayList<>(); + for (LiveDataQuery.Filter queryFilter : queryFilters) { + switch (queryFilter.getProperty()) { + case NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> { + // We only check first constraint: if there's more they are either redundant or contradictory. + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { + queryWhereClauses.add("nfp.enabled = 1"); + } else { + queryWhereClauses.add("nfp.enabled = 0"); + } + } + + case NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { + String formatFilter = handleFormatFilter(queryFilter); + if (formatFilter != null) { + queryWhereClauses.add(formatFilter); + } + } + + case NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + NotificationFiltersLiveDataConfigurationProvider.Scope scope = + NotificationFiltersLiveDataConfigurationProvider.Scope.valueOf( + String.valueOf(constraint.getValue())); + String fieldName = ""; + switch (scope) { + case WIKI -> fieldName = "wiki"; + case SPACE -> fieldName = "page"; + case PAGE -> fieldName = "pageOnly"; + case USER -> fieldName = "user"; + } + queryWhereClauses.add(String.format("len(nfp.%s) > 0", fieldName)); + } + } + + case NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + queryWhereClauses.add("nfp.filterType = :filterType"); + result.bindings.put("filterType", constraint.getValue()); + } + } + // TODO: handle other filters + } + } + if (!queryWhereClauses.isEmpty()) { + StringBuilder stringBuilder = new StringBuilder(" where "); + Iterator iterator = queryWhereClauses.iterator(); + while (iterator.hasNext()) { + stringBuilder.append(iterator.next()); + if (iterator.hasNext()) { + stringBuilder.append(" and "); + } + } + result.whereClause = stringBuilder.toString(); + return Optional.of(result); + } else { + return Optional.empty(); + } + } + + private String handleFormatFilter(LiveDataQuery.Filter formatFilter) + { + List clauses = new ArrayList<>(); + for (LiveDataQuery.Constraint constraint : formatFilter.getConstraints()) { + boolean isEmailEnabled = false; + boolean isAlertEnabled = false; + String constraintValue = String.valueOf(constraint.getValue()); + if (StringUtils.isNotBlank(constraintValue)) { + if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { + isEmailEnabled = EMAIL_FORMAT.startsWith(constraintValue); + isAlertEnabled = ALERT_FORMAT.startsWith(constraintValue); + } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { + isEmailEnabled = EMAIL_FORMAT.contains(constraintValue); + isAlertEnabled = ALERT_FORMAT.contains(constraintValue); + } else if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + isEmailEnabled = EMAIL_FORMAT.equals(constraintValue); + isAlertEnabled = ALERT_FORMAT.equals(constraintValue); + } + if (isEmailEnabled) { + clauses.add("nfp.emailEnabled = 1"); + } else { + clauses.add("nfp.emailEnabled = 0"); + } + if (isAlertEnabled) { + clauses.add("nfp.alertEnabled = 1"); + } else { + clauses.add("nfp.alertEnabled = 0"); + } + } + } + if (!clauses.isEmpty()) { + StringBuilder result = new StringBuilder(); + String operatorAppender; + if (formatFilter.isMatchAll()) { + operatorAppender = " and "; + } else { + operatorAppender = " or "; + } + Iterator iterator = clauses.iterator(); + while (iterator.hasNext()) { + result.append(iterator.next()); + if (iterator.hasNext()) { + result.append(operatorAppender); + } + } + return result.toString(); + } else { + return null; + } + } + + private String handleSortEntries(List sortEntries) + { + List clauses = new ArrayList<>(); + for (LiveDataQuery.SortEntry sortEntry : sortEntries) { + String sortOperator = "asc"; + if (sortEntry.isDescending()) { + sortOperator = "desc"; + } + switch (sortEntry.getProperty()) { + case NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> + clauses.add(String.format("nfp.enabled %s", sortOperator)); + + case NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { + if (sortEntry.isDescending()) { + clauses.add("nfp.alertEnabled desc, nfp.emailEnabled asc"); + } else { + clauses.add("nfp.alertEnabled asc, nfp.emailEnabled desc"); + } + } + + case NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { + if (sortEntry.isDescending()) { + clauses.add("nfp.user desc, nfp.wiki desc, nfp.page desc, nfp.pageOnly desc"); + } else { + clauses.add("nfp.pageOnly asc, nfp.page asc, nfp.wiki asc, nfp.user asc"); + } + } + + case NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> { + if (sortEntry.isDescending()) { + clauses.add("nfp.filterType desc"); + } else { + clauses.add("nfp.filterType asc"); + } + } + // TODO: handle other columns + } + } + if (!clauses.isEmpty()) { + StringBuilder result = new StringBuilder(" order by "); + Iterator iterator = clauses.iterator(); + while (iterator.hasNext()) { + result.append(iterator.next()); + if (iterator.hasNext()) { + result.append(", "); + } + } + return result.toString(); + } else { + return ""; + } + } + + @Override + public LiveData get(LiveDataQuery query) throws LiveDataException + { + if (query.getOffset() > Integer.MAX_VALUE) { + throw new LiveDataException("Currently only integer offsets are supported."); + } + LiveData liveData = new LiveData(); + String baseQuery = "select nfp from DefaultNotificationFilterPreference nfp "; + Optional optionalFiltersHQLQuery = handleFilter(query.getFilters()); + if (optionalFiltersHQLQuery.isPresent()) { + baseQuery += optionalFiltersHQLQuery.get().whereClause; + } + baseQuery += handleSortEntries(query.getSort()); + try { + // FIXME: handle wiki + Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL); + if (optionalFiltersHQLQuery.isPresent()) { + for (Map.Entry binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { + hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); + } + } + List notificationFilterPreferences = hqlQuery + .setLimit(query.getLimit()) + .setOffset(query.getOffset().intValue()) + .execute(); + liveData.setCount(notificationFilterPreferences.size()); + List> entries = liveData.getEntries(); + for (NotificationFilterPreference notificationFilterPreference : notificationFilterPreferences) { + entries.add(getPreferenceInformation(notificationFilterPreference)); + } + } catch (QueryException | NotificationException e) { + throw new LiveDataException("Error when querying notification filter preferences", e); + } + return liveData; + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataPropertyDescriptorStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataPropertyDescriptorStore.java new file mode 100644 index 000000000000..80e9a16b72a2 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataPropertyDescriptorStore.java @@ -0,0 +1,54 @@ +/* + * 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 javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataConfiguration; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.LiveDataPropertyDescriptor; +import org.xwiki.livedata.LiveDataPropertyDescriptorStore; + +/** + * Descriptor for the {@link NotificationFiltersLiveDataSource}. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationFiltersLiveDataSource.NAME) +public class NotificationFiltersLiveDataPropertyDescriptorStore implements LiveDataPropertyDescriptorStore +{ + @Inject + @Named(NotificationFiltersLiveDataSource.NAME) + private Provider liveDataConfigurationProvider; + + @Override + public Collection get() throws LiveDataException + { + return liveDataConfigurationProvider.get().getMeta().getPropertyDescriptors(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java new file mode 100644 index 000000000000..8e02c0ee63b5 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java @@ -0,0 +1,58 @@ +/* + * 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 javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataEntryStore; +import org.xwiki.livedata.LiveDataPropertyDescriptorStore; +import org.xwiki.livedata.LiveDataSource; + +@Component +@Singleton +@Named(NotificationFiltersLiveDataSource.NAME) +public class NotificationFiltersLiveDataSource implements LiveDataSource +{ + static final String NAME = "notificationFilters"; + + @Inject + @Named(NAME) + private LiveDataEntryStore wordsQueryLiveDataEntryStore; + + @Inject + @Named(NAME) + private LiveDataPropertyDescriptorStore wordsQueryLiveDataPropertyDescriptorStore; + + @Override + public LiveDataEntryStore getEntries() + { + return this.wordsQueryLiveDataEntryStore; + } + + @Override + public LiveDataPropertyDescriptorStore getProperties() + { + return this.wordsQueryLiveDataPropertyDescriptorStore; + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt index 15b7154af1d4..89ad18fbaf29 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt @@ -1,15 +1,20 @@ +org.xwiki.notifications.filters.internal.event.NotificationFilterPreferenceEventConverter +org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataConfigurationProvider +org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataConfigurationResolver +org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataEntryStore +org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataPropertyDescriptorStore +org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataSource +org.xwiki.notifications.filters.internal.migrators.NotificationFilterPreferencesMigrator +org.xwiki.notifications.filters.internal.migrators.ScopeNotificationFilterClassMigrator org.xwiki.notifications.filters.internal.CachedModelBridgeInvalidatorListener org.xwiki.notifications.filters.internal.DefaultFilterPreferencesModelBridge org.xwiki.notifications.filters.internal.DocumentMovedListener org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore org.xwiki.notifications.filters.internal.ToggleableFilterPreferenceDocumentInitializer +org.xwiki.notifications.filters.internal.UserAddedEventListener org.xwiki.notifications.filters.internal.WikiNotificationFilterDisplayer org.xwiki.notifications.filters.internal.WikiNotificationFilterDisplayerComponentBuilder org.xwiki.notifications.filters.internal.WikiNotificationFilterDisplayerDocumentInitializer -org.xwiki.notifications.filters.internal.event.NotificationFilterPreferenceEventConverter -org.xwiki.notifications.filters.internal.migrators.NotificationFilterPreferencesMigrator -org.xwiki.notifications.filters.internal.migrators.ScopeNotificationFilterClassMigrator -org.xwiki.notifications.filters.internal.UserAddedEventListener org.xwiki.notifications.filters.migration.R140401000XWIKI15460DataMigration org.xwiki.notifications.filters.migration.R151002000XWIKI21448DataMigration org.xwiki.notifications.filters.migration.R160000000XWIKI17243DataMigration diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index d5645352c095..34229a10509b 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -469,6 +469,7 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', 'xwiki-events-bridge' ], function ($, xm, Vue, notifications, l10n) { Vue.component('DisplayerToggle', notifications.DisplayerToggle); + Vue.component('DisplayerScope', notifications.DisplayerScope); // Globals var serviceReference = XWiki.Model.resolve('XWiki.Notifications.Code.NotificationPreferenceService', @@ -800,38 +801,18 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', </div> #set ($sourceParameters = { 'translationPrefix': 'notifications.settings.filters.preferences.custom.table.', - 'resultPage': 'XWiki.Notifications.Code.NotificationFilterPreferenceLivetableResults', - 'eventType': '', - 'format': '', 'type': 'custom' }) #if ($wikimacro.parameters.target == 'user') #set ($discard = $sourceParameters.put('user', $services.model.serialize($targetUser, 'default'))) #end - #set ($liveDataConfig = { - 'meta': { - 'propertyDescriptors': [ - { 'id': 'notificationFormats', 'displayer': 'html', 'filterable': false, 'sortable': false }, - { 'id': 'isEnabled', 'displayer': 'toggle', 'filterable': false, 'sortable': false }, - { 'id': 'name', 'displayer': 'html', 'filterable': false, 'sortable': false}, - { 'id': 'filterType', 'filterable': false, 'sortable': false}, - { 'id': 'eventTypes', 'displayer': 'html', 'filterable': false, 'sortable': false} - ], - 'actions': [ - {'id': 'delete', 'allowProperty': 'doc.viewable'} - ], - 'entryDescriptor': { - 'idProperty': 'filterPreferenceId' - } - } - }) $services.liveData.render({ 'id': 'notificationCustomFilterPreferencesLiveData', - 'properties': 'name,filterType,eventTypes,notificationFormats,isEnabled,_actions', - 'source': 'liveTable', + 'properties': 'scope,location,filterType,eventTypes,notificationFormats,isEnabled', + 'source': 'notificationFilters', 'sourceParameters': $escapetool.url($sourceParameters), 'limit': 10 - }, $liveDataConfig) + }) </div> ###################################################### ### ADD FILTER MODAL diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue new file mode 100644 index 000000000000..e6ba90bd4a90 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue @@ -0,0 +1,59 @@ + + + + + + \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js index fc8ddcb896a3..edc65b39e77a 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js @@ -18,6 +18,7 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ import DisplayerToggle from "./components/DisplayerToggle.vue"; +import DisplayerScope from "./components/DisplayerScope.vue"; // Export the elements that are expected to be imported from other modules. -export {DisplayerToggle} \ No newline at end of file +export {DisplayerToggle,DisplayerScope} \ No newline at end of file From 213d028ac987ba3f73486bf65267b4f38ad83a6e Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Tue, 5 Mar 2024 18:37:30 +0100 Subject: [PATCH 02/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source WIP --- ...faultRecordableEventDescriptorManager.java | 1 + ...cationFilterLiveDataTranslationHelper.java | 85 ++++++++ ...nFiltersLiveDataConfigurationProvider.java | 70 +++--- ...NotificationFiltersLiveDataEntryStore.java | 206 ++++++++++++------ .../main/resources/META-INF/components.txt | 1 + .../XWiki/Notifications/Code/Translations.xml | 42 ++-- .../vue/src/components/DisplayerScope.vue | 9 +- 7 files changed, 285 insertions(+), 129 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java diff --git a/xwiki-platform-core/xwiki-platform-eventstream/xwiki-platform-eventstream-api/src/main/java/org/xwiki/eventstream/internal/DefaultRecordableEventDescriptorManager.java b/xwiki-platform-core/xwiki-platform-eventstream/xwiki-platform-eventstream-api/src/main/java/org/xwiki/eventstream/internal/DefaultRecordableEventDescriptorManager.java index 354f1e41ecf9..cb28d9ba6cbd 100644 --- a/xwiki-platform-core/xwiki-platform-eventstream/xwiki-platform-eventstream-api/src/main/java/org/xwiki/eventstream/internal/DefaultRecordableEventDescriptorManager.java +++ b/xwiki-platform-core/xwiki-platform-eventstream/xwiki-platform-eventstream-api/src/main/java/org/xwiki/eventstream/internal/DefaultRecordableEventDescriptorManager.java @@ -103,6 +103,7 @@ public List getRecordableEventDescriptors(boolean all public RecordableEventDescriptor getDescriptorForEventType(String eventType, boolean allWikis) throws EventStreamException { + // FIXME: We should cache the descriptors for improving perf when calling multiple times this method. return getRecordableEventDescriptors(allWikis).stream().filter(descriptor -> eventType.equals(descriptor.getEventType())).findAny().orElse(null); } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java new file mode 100644 index 000000000000..16d410bdd40e --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -0,0 +1,85 @@ +/* + * 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 javax.inject.Inject; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.eventstream.EventStreamException; +import org.xwiki.eventstream.RecordableEventDescriptor; +import org.xwiki.eventstream.RecordableEventDescriptorManager; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.localization.ContextualLocalizationManager; +import org.xwiki.notifications.filters.NotificationFilterType; + +@Component(roles = NotificationFilterLiveDataTranslationHelper.class) +@Singleton +public class NotificationFilterLiveDataTranslationHelper +{ + @Inject + private ContextualLocalizationManager contextualLocalizationManager; + + @Inject + private RecordableEventDescriptorManager recordableEventDescriptorManager; + + private String getTranslationWithFallback(String translationKey) + { + String translationPlain = this.contextualLocalizationManager.getTranslationPlain(translationKey); + if (translationPlain == null) { + translationPlain = translationKey; + } + return translationPlain; + } + + public String getTranslationWithPrefix(String prefix, String key) + { + return getTranslationWithFallback(prefix + key); + } + + public String getFilterTypeTranslation(NotificationFilterType filterType) + { + return getTranslationWithPrefix("notifications.filters.type.custom.", filterType.name().toLowerCase()); + } + + public String getScopeTranslation(NotificationFiltersLiveDataConfigurationProvider.Scope scope) + { + return getTranslationWithPrefix("notifications.filters.preferences.scopeNotificationFilter.", + scope.name().toLowerCase()); + } + + public String getAllEventTypesTranslation() + { + return getTranslationWithFallback("notifications.filters.preferences.allEvents"); + } + + public String getEventTypeTranslation(String eventType) throws LiveDataException + { + 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); + } + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java index ebbe781a25d0..762af40e5b08 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java @@ -67,12 +67,23 @@ public class NotificationFiltersLiveDataConfigurationProvider implements Provide static final String DOC_VIEWABLE_FIELD = "doc_viewable"; static final String DOC_HAS_DELETE_FIELD = "doc_hasdelete"; - enum Scope + public enum Scope { - WIKI, - SPACE, - PAGE, - USER + WIKI("wiki"), + SPACE("page"), + PAGE("pageOnly"), + USER("user"); + + private final String fieldName; + Scope(String fieldName) + { + this.fieldName = fieldName; + } + + String getFieldName() + { + return this.fieldName; + } }; private static final String REMOVE = "remove"; @@ -82,6 +93,9 @@ enum Scope @Inject private ContextualLocalizationManager l10n; + @Inject + private NotificationFilterLiveDataTranslationHelper translationHelper; + @Inject private IconManager iconManager; @@ -135,7 +149,6 @@ public LiveDataConfiguration get() private LiveDataPropertyDescriptor getDisplayDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - // FIXME: this translation should be changed descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "name")); descriptor.setId(DISPLAY_FIELD); descriptor.setType(STRING_TYPE); @@ -151,7 +164,6 @@ private LiveDataPropertyDescriptor getDisplayDescriptor() private LiveDataPropertyDescriptor getScopeDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - // FIXME: this translation should be changed descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "scope")); descriptor.setId(SCOPE_FIELD); descriptor.setType(STRING_TYPE); @@ -159,20 +171,13 @@ private LiveDataPropertyDescriptor getScopeDescriptor() LiveDataPropertyDescriptor.FilterDescriptor filterList = new LiveDataPropertyDescriptor.FilterDescriptor("list"); filterList.addOperator("empty", null); - // TODO: Provide a generic component for those translations? - String translationPrefix = "notifications.filters.preferences.scopeNotificationFilter."; filterList.setParameter("options", Stream.of(Scope.values()) - .map(item -> { - String label = l10n.getTranslationPlain(translationPrefix + item.name().toLowerCase()); - if (label == null) { - label = item.name().toLowerCase(); - } - return Map.of( - "value", item.name(), - "label", label - ); - }).collect(Collectors.toList())); + .map(item -> Map.of( + "value", item.name(), + "label", this.translationHelper.getScopeTranslation(item) + )).collect(Collectors.toList())); filterList.addOperator("equals", null); + filterList.setDefaultOperator("equals"); descriptor.setFilter(filterList); descriptor.setVisible(true); descriptor.setEditable(false); @@ -185,7 +190,6 @@ private LiveDataPropertyDescriptor getScopeDescriptor() private LiveDataPropertyDescriptor getLocationDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - // FIXME: this translation should be changed descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "location")); descriptor.setId(LOCATION_FIELD); descriptor.setType(STRING_TYPE); @@ -208,19 +212,13 @@ private LiveDataPropertyDescriptor getFilterTypeDescriptor() LiveDataPropertyDescriptor.FilterDescriptor filterList = new LiveDataPropertyDescriptor.FilterDescriptor("list"); filterList.addOperator("empty", null); - String translationPrefix = "notifications.filters.type.custom."; filterList.setParameter("options", Stream.of(NotificationFilterType.values()) - .map(item -> { - String label = l10n.getTranslationPlain(translationPrefix + item.name().toLowerCase()); - if (label == null) { - label = item.name().toLowerCase(); - } - return Map.of( - "value", item.name(), - "label", label - ); - }).collect(Collectors.toList())); + .map(item -> Map.of( + "value", item.name(), + "label", this.translationHelper.getFilterTypeTranslation(item) + )).collect(Collectors.toList())); filterList.addOperator("equals", null); + filterList.setDefaultOperator("equals"); descriptor.setFilter(filterList); descriptor.setVisible(true); descriptor.setEditable(false); @@ -235,9 +233,8 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "notificationFormats")); descriptor.setId(NOTIFICATION_FORMATS_FIELD); - descriptor.setType(LIST_TYPE); - // FIXME: We should provide a new custom displayer for this. - //descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("notificationFormats")); + descriptor.setType(STRING_TYPE); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("html")); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -251,9 +248,8 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor() LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "eventTypes")); descriptor.setId(EVENT_TYPES_FIELD); - descriptor.setType(LIST_TYPE); - // FIXME: We should provide a new custom displayer for this. - //descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("eventTypes")); + descriptor.setType(STRING_TYPE); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("html")); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java index a850ed124c1a..aa2b1c1c1a6d 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; import javax.inject.Named; @@ -33,6 +34,10 @@ import org.apache.commons.lang3.StringUtils; import org.xwiki.component.annotation.Component; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.eventstream.EventStreamException; +import org.xwiki.eventstream.RecordableEventDescriptor; +import org.xwiki.eventstream.RecordableEventDescriptorManager; import org.xwiki.livedata.LiveData; import org.xwiki.livedata.LiveDataEntryStore; import org.xwiki.livedata.LiveDataException; @@ -44,14 +49,16 @@ import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationException; +import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.NotificationFilterPreference; +import org.xwiki.notifications.filters.NotificationFilterType; import org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore; -import org.xwiki.notifications.filters.internal.scope.ScopeNotificationFilter; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.query.QueryManager; +import org.xwiki.query.internal.DefaultQueryParameter; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.renderer.BlockRenderer; import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; @@ -92,8 +99,8 @@ public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore private EntityReferenceSerializer entityReferenceSerializer; @Inject - @Named(ScopeNotificationFilter.FILTER_NAME) - private NotificationFilter scopeNotificationFilter; + @Named("context") + private ComponentManager componentManager; @Inject @Named("html/5.0") @@ -103,7 +110,7 @@ public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore private QueryManager queryManager; @Inject - private ContextualLocalizationManager localizationManager; + private NotificationFilterLiveDataTranslationHelper translationHelper; @Override public Optional> get(Object entryId) throws LiveDataException @@ -142,24 +149,57 @@ private Map getPreferenceInformation(NotificationFilterPreferenc } return Map.of( NotificationFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId(), - NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, filterPreference.getEventTypes(), + NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, + this.displayEventTypes(filterPreference), NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, - filterPreference.getNotificationFormats(), + displayNotificationFormats(filterPreference), NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, filterPreference.isEnabled(), NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope), NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, this.entityReferenceSerializer.serialize(location), NotificationFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, this.renderDisplay(filterPreference), NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, - localizationManager.getTranslationPlain("notifications.filters.type.custom." + - filterPreference.getFilterType().name().toLowerCase()) + this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType()) ); } + private String displayEventTypes(NotificationFilterPreference filterPreference) throws LiveDataException + { + StringBuilder result = new StringBuilder("
    "); + Set eventTypes = filterPreference.getEventTypes(); + if (eventTypes.isEmpty()) { + result.append("
  • "); + result.append(this.translationHelper.getAllEventTypesTranslation()); + result.append("
  • "); + } else { + for (String eventType : eventTypes) { + result.append("
  • "); + result.append(this.translationHelper.getEventTypeTranslation(eventType)); + result.append("
  • "); + } + } + result.append("
"); + return result.toString(); + } + + private String displayNotificationFormats(NotificationFilterPreference filterPreference) + { + StringBuilder result = new StringBuilder("
    "); + String translationPrefix = "notifications.format."; + + for (NotificationFormat notificationFormat : filterPreference.getNotificationFormats()) { + result.append("
  • "); + result.append(this.translationHelper.getTranslationWithPrefix(translationPrefix, + notificationFormat.name().toLowerCase())); + result.append("
  • "); + } + result.append("
"); + + return result.toString(); + } + private Map getScopeInfo(NotificationFiltersLiveDataConfigurationProvider.Scope scope) { - String translationPrefix = "notifications.filters.preferences.scopeNotificationFilter."; - String name = translationPrefix + scope.name().toLowerCase(); String icon = ""; switch (scope) { case WIKI -> icon = "wiki"; @@ -167,23 +207,29 @@ private Map getScopeInfo(NotificationFiltersLiveDataConfiguratio case PAGE -> icon = "page"; case USER -> icon = "user"; } - return Map.of("icon", icon, "name", this.localizationManager.getTranslationPlain(name)); + return Map.of("icon", icon, "name", this.translationHelper.getScopeTranslation(scope)); } - private String renderDisplay(NotificationFilterPreference filterPreference) - throws NotificationException, LiveDataException + private String renderDisplay(NotificationFilterPreference filterPreference) throws LiveDataException { - String result; + String result = ""; WikiPrinter printer = new DefaultWikiPrinter(); - // FIXME: here I hardcode the fact that the pref is for a ScopeNotificationFilter, need to check if that's - // always true - Block block = this.notificationFilterManager.displayFilter(scopeNotificationFilter, filterPreference); - try { - blockRenderer.render(block, printer); - result = printer.toString(); - } catch (Exception e) { - throw new LiveDataException("Error while rendering a block for notification filter", e); + String missingComponentExceptionMessage = String.format("Cannot find NotificationFilter component for " + + "preference named [%s]", filterPreference.getFilterName()); + if (this.componentManager.hasComponent(NotificationFilter.class, filterPreference.getFilterName())) { + try { + NotificationFilter filter = + this.componentManager.getInstance(NotificationFilter.class, filterPreference.getFilterName()); + Block block = this.notificationFilterManager.displayFilter(filter, filterPreference); + blockRenderer.render(block, printer); + result = printer.toString(); + } catch (Exception e) { + throw new LiveDataException("Error while rendering a block for notification filter", e); + } + } else { + throw new LiveDataException(missingComponentExceptionMessage); } + return result; } @@ -224,10 +270,7 @@ private Optional handleFilter(List queryF } case NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { - String formatFilter = handleFormatFilter(queryFilter); - if (formatFilter != null) { - queryWhereClauses.add(formatFilter); - } + handleFormatFilter(queryFilter, queryWhereClauses); } case NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { @@ -237,14 +280,7 @@ private Optional handleFilter(List queryF NotificationFiltersLiveDataConfigurationProvider.Scope scope = NotificationFiltersLiveDataConfigurationProvider.Scope.valueOf( String.valueOf(constraint.getValue())); - String fieldName = ""; - switch (scope) { - case WIKI -> fieldName = "wiki"; - case SPACE -> fieldName = "page"; - case PAGE -> fieldName = "pageOnly"; - case USER -> fieldName = "user"; - } - queryWhereClauses.add(String.format("len(nfp.%s) > 0", fieldName)); + queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); } } @@ -253,10 +289,19 @@ private Optional handleFilter(List queryF LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); if (EQUALS_OPERATOR.equals(constraint.getOperator())) { queryWhereClauses.add("nfp.filterType = :filterType"); - result.bindings.put("filterType", constraint.getValue()); + result.bindings.put("filterType", + NotificationFilterType.valueOf(String.valueOf(constraint.getValue()))); + } + } + + case NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> { + Optional locationQueryOpt = this.handleLocationFilter(queryFilter); + if (locationQueryOpt.isPresent()) { + FiltersHQLQuery locationHQLQuery = locationQueryOpt.get(); + queryWhereClauses.add(locationHQLQuery.whereClause); + result.bindings.putAll(locationHQLQuery.bindings); } } - // TODO: handle other filters } } if (!queryWhereClauses.isEmpty()) { @@ -275,7 +320,63 @@ private Optional handleFilter(List queryF } } - private String handleFormatFilter(LiveDataQuery.Filter formatFilter) + private Optional handleLocationFilter(LiveDataQuery.Filter locationFilter) + { + FiltersHQLQuery filtersHQLQuery = new FiltersHQLQuery(); + List clauses = new ArrayList<>(); + int clauseCounter = 0; + String clauseValue = "(nfp.pageOnly like :constraint_%1$s or nfp.page like :constraint_%1$s or " + + "nfp.wiki like :constraint_%1$s or nfp.user like :constraint_%1$s)"; + String constraintName = "constraint_%s"; + for (LiveDataQuery.Constraint constraint : locationFilter.getConstraints()) { + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format("(nfp.pageOnly = :constraint_%1$s or nfp.page = :constraint_%1$s or " + + "nfp.wiki = :constraint_%1$s or nfp.user = :constraint_%1$s)", clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.literal(String.valueOf(constraint.getValue())); + filtersHQLQuery.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } else if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format(clauseValue, clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.literal(String.valueOf(constraint.getValue())).anyChars(); + filtersHQLQuery.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format(clauseValue, clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); + filtersHQLQuery.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } + clauseCounter++; + } + if (!clauses.isEmpty()) { + filtersHQLQuery.whereClause = buildQueryClause(locationFilter, clauses); + return Optional.of(filtersHQLQuery); + } else { + return Optional.empty(); + } + } + + private String buildQueryClause(LiveDataQuery.Filter filter, List clauses) + { + StringBuilder result = new StringBuilder("("); + String operatorAppender; + if (filter.isMatchAll()) { + operatorAppender = " and "; + } else { + operatorAppender = " or "; + } + Iterator iterator = clauses.iterator(); + while (iterator.hasNext()) { + result.append(iterator.next()); + if (iterator.hasNext()) { + result.append(operatorAppender); + } + } + result.append(")"); + return result.toString(); + } + + private void handleFormatFilter(LiveDataQuery.Filter formatFilter, List queryWhereClauses) { List clauses = new ArrayList<>(); for (LiveDataQuery.Constraint constraint : formatFilter.getConstraints()) { @@ -306,23 +407,7 @@ private String handleFormatFilter(LiveDataQuery.Filter formatFilter) } } if (!clauses.isEmpty()) { - StringBuilder result = new StringBuilder(); - String operatorAppender; - if (formatFilter.isMatchAll()) { - operatorAppender = " and "; - } else { - operatorAppender = " or "; - } - Iterator iterator = clauses.iterator(); - while (iterator.hasNext()) { - result.append(iterator.next()); - if (iterator.hasNext()) { - result.append(operatorAppender); - } - } - return result.toString(); - } else { - return null; + queryWhereClauses.add(buildQueryClause(formatFilter, clauses)); } } @@ -346,7 +431,8 @@ private String handleSortEntries(List sortEntries) } } - case NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { + case NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, + NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { if (sortEntry.isDescending()) { clauses.add("nfp.user desc, nfp.wiki desc, nfp.page desc, nfp.pageOnly desc"); } else { @@ -354,14 +440,8 @@ private String handleSortEntries(List sortEntries) } } - case NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> { - if (sortEntry.isDescending()) { - clauses.add("nfp.filterType desc"); - } else { - clauses.add("nfp.filterType asc"); - } - } - // TODO: handle other columns + case NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> + clauses.add(String.format("nfp.filterType %s", sortOperator)); } } if (!clauses.isEmpty()) { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt index 89ad18fbaf29..3f90af5e7e8c 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt @@ -1,4 +1,5 @@ org.xwiki.notifications.filters.internal.event.NotificationFilterPreferenceEventConverter +org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataConfigurationProvider org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataConfigurationResolver org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataEntryStore diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml index 72dc9ae589e8..90f024438731 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml @@ -1,36 +1,19 @@ - - - - - + + XWiki.Notifications.Code Translations en 0 - xwiki:XWiki.Admin + XWiki.superadmin + 1709550629000 XWiki.Notifications.Code.WebHome - xwiki:XWiki.Admin - xwiki:XWiki.Admin - 1.1 + XWiki.Admin + XWiki.Admin + XWiki.Admin + 1709659831000 + 1709659831000 + 2.1 <comment/> <minorEdit>false</minorEdit> @@ -103,6 +86,9 @@ notifications.settings.filters.preferences.custom.title=Custom Filters notifications.settings.filters.preferences.custom.hint=Create filters to control the notifications you want to receive. ## Header definition for notification filter preferences notifications.settings.filters.preferences.custom.table.name=Location +notifications.settings.filters.preferences.custom.table.location=Location +notifications.settings.filters.preferences.custom.table.scope=Scope +notifications.settings.filters.preferences.custom.table.location=Location notifications.settings.filters.preferences.custom.table.filterType=Filter Action notifications.settings.filters.preferences.custom.table.eventTypes=Events notifications.settings.filters.preferences.custom.table.notificationFormats=Formats @@ -308,4 +294,4 @@ notifications.settings.filters.preferences.system.table.description=This table l <scope>WIKI</scope> </property> </object> -</xwikidoc> +</xwikidoc> \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue index e6ba90bd4a90..fc1b2282969f 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue @@ -56,4 +56,11 @@ export default { // Add the displayerMixin to get access to all the displayers methods and computed properties inside this component. mixins: [displayerMixin] } -</script> \ No newline at end of file +</script> +<style> + +.scope-name { + margin-left: 1em; +} + +</style> \ No newline at end of file From e294b945e3cf70bb1c9a9a250deef7f4f7abb84c Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Wed, 6 Mar 2024 16:07:49 +0100 Subject: [PATCH 03/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source WIP --- ...cationFilterLiveDataTranslationHelper.java | 19 ++++++ ...nFiltersLiveDataConfigurationProvider.java | 37 ++++++++--- ...NotificationFiltersLiveDataEntryStore.java | 65 +++++++++++++++---- ...ficationsCustomFiltersPreferencesMacro.xml | 1 + .../XWiki/Notifications/Code/Translations.xml | 1 + .../vue/src/components/DisplayerScope.vue | 9 ++- .../notification/filters/livedatalocation.vm | 21 ++++++ 7 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/notification/filters/livedatalocation.vm diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index 16d410bdd40e..3da37b20624d 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -20,6 +20,11 @@ package org.xwiki.notifications.filters.internal.livedata; +import java.util.List; +import java.util.Map; +import java.util.stream.Collector; +import java.util.stream.Collectors; + import javax.inject.Inject; import javax.inject.Singleton; @@ -82,4 +87,18 @@ public String getEventTypeTranslation(String eventType) throws LiveDataException String.format("Error while getting description for event type [%s]", eventType), e); } } + + public List<Map<String, String>> getAllEventTypesOptions(boolean allFarm) throws LiveDataException + { + try { + List<RecordableEventDescriptor> recordableEventDescriptors = + this.recordableEventDescriptorManager.getRecordableEventDescriptors(allFarm); + return recordableEventDescriptors.stream().map(descriptor -> Map.of( + "value", descriptor.getEventType(), + "label", getTranslationWithFallback(descriptor.getDescription()) + )).collect(Collectors.toList()); + } catch (EventStreamException e) { + throw new LiveDataException("Error while retrieving event descriptors", e); + } + } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java index 762af40e5b08..353f6f203574 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java @@ -19,7 +19,9 @@ */ package org.xwiki.notifications.filters.internal.livedata; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -35,6 +37,7 @@ import org.xwiki.icon.IconManager; import org.xwiki.livedata.LiveDataConfiguration; import org.xwiki.livedata.LiveDataEntryDescriptor; +import org.xwiki.livedata.LiveDataException; import org.xwiki.livedata.LiveDataMeta; import org.xwiki.livedata.LiveDataPaginationConfiguration; import org.xwiki.livedata.LiveDataPropertyDescriptor; @@ -53,7 +56,7 @@ @Named(NotificationFiltersLiveDataSource.NAME) public class NotificationFiltersLiveDataConfigurationProvider implements Provider<LiveDataConfiguration> { - private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.custom.table."; + public static final String ALL_EVENTS_OPTION_VALUE = "__ALL_EVENTS__"; static final String ID_FIELD = "filterPreferenceId"; static final String SCOPE_FIELD = "scope"; static final String LOCATION_FIELD = "location"; @@ -66,6 +69,13 @@ public class NotificationFiltersLiveDataConfigurationProvider implements Provide static final String IS_ENABLED_CHECKED_FIELD = "isEnabled_checked"; static final String DOC_VIEWABLE_FIELD = "doc_viewable"; 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 REMOVE = "remove"; + private static final String STRING_TYPE = "String"; + private static final String HTML_DISPLAYER = "html"; + private static final String VALUE_KEY = "value"; + private static final String LABEL_KEY = "label"; + public enum Scope { @@ -86,10 +96,6 @@ String getFieldName() } }; - private static final String REMOVE = "remove"; - private static final String STRING_TYPE = "String"; - private static final String LIST_TYPE = "java.util.List"; - @Inject private ContextualLocalizationManager l10n; @@ -193,8 +199,7 @@ private LiveDataPropertyDescriptor getLocationDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "location")); descriptor.setId(LOCATION_FIELD); descriptor.setType(STRING_TYPE); - // FIXME: We should provide a new custom location displayer for LD. - //descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("location")); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -249,7 +254,23 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "eventTypes")); descriptor.setId(EVENT_TYPES_FIELD); descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("html")); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); + LiveDataPropertyDescriptor.FilterDescriptor filterList = + new LiveDataPropertyDescriptor.FilterDescriptor("list"); + filterList.addOperator("empty", null); + List<Map<String, String>> options = new ArrayList<>(); + options.add(Map.of( + VALUE_KEY, ALL_EVENTS_OPTION_VALUE, + LABEL_KEY, this.translationHelper.getAllEventTypesTranslation())); + try { + options.addAll(this.translationHelper.getAllEventTypesOptions(true)); + } catch (LiveDataException e) { + this.logger.error("Cannot provide event filter options", e); + } + filterList.setParameter("options", options); + filterList.addOperator("equals", null); + filterList.setDefaultOperator("equals"); + descriptor.setFilter(filterList); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java index aa2b1c1c1a6d..ce1f6d9b8fa3 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java @@ -31,18 +31,15 @@ import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; +import javax.script.ScriptContext; import org.apache.commons.lang3.StringUtils; import org.xwiki.component.annotation.Component; import org.xwiki.component.manager.ComponentManager; -import org.xwiki.eventstream.EventStreamException; -import org.xwiki.eventstream.RecordableEventDescriptor; -import org.xwiki.eventstream.RecordableEventDescriptorManager; import org.xwiki.livedata.LiveData; import org.xwiki.livedata.LiveDataEntryStore; import org.xwiki.livedata.LiveDataException; import org.xwiki.livedata.LiveDataQuery; -import org.xwiki.localization.ContextualLocalizationManager; import org.xwiki.model.EntityType; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceResolver; @@ -63,6 +60,8 @@ import org.xwiki.rendering.renderer.BlockRenderer; import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; import org.xwiki.rendering.renderer.printer.WikiPrinter; +import org.xwiki.script.ScriptContextManager; +import org.xwiki.template.TemplateManager; import com.xpn.xwiki.XWikiContext; @@ -82,6 +81,7 @@ public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore private static final String EQUALS_OPERATOR = "equals"; private static final String EMAIL_FORMAT = "email"; private static final String ALERT_FORMAT = "alert"; + private static final String LOCATION_TEMPLATE = "notification/filters/livedatalocation.vm"; @Inject private NotificationFilterPreferenceStore notificationFilterPreferenceStore; @@ -112,6 +112,12 @@ public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore @Inject private NotificationFilterLiveDataTranslationHelper translationHelper; + @Inject + private TemplateManager templateManager; + + @Inject + private ScriptContextManager scriptContextManager; + @Override public Optional<Map<String, Object>> get(Object entryId) throws LiveDataException { @@ -138,15 +144,8 @@ private Map<String, Object> getPreferenceInformation(NotificationFilterPreferenc throws NotificationException, LiveDataException { NotificationFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); - EntityReference location = null; - switch (scope) { - case WIKI -> location = this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); - case SPACE -> location = this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); - case PAGE -> location = this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), - EntityType.DOCUMENT); - case USER -> location = this.entityReferenceResolver.resolve(filterPreference.getUser(), - EntityType.DOCUMENT); - } + + // FIXME: Check authorization? return Map.of( NotificationFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId(), NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, @@ -156,13 +155,31 @@ private Map<String, Object> getPreferenceInformation(NotificationFilterPreferenc NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, filterPreference.isEnabled(), NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope), NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, - this.entityReferenceSerializer.serialize(location), + this.displayLocation(filterPreference, scope), NotificationFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, this.renderDisplay(filterPreference), NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType()) ); } + private String displayLocation(NotificationFilterPreference filterPreference, + NotificationFiltersLiveDataConfigurationProvider.Scope scope) + { + EntityReference location = null; + switch (scope) { + case WIKI -> location = this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); + case SPACE -> location = this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); + case PAGE -> location = this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), + EntityType.DOCUMENT); + case USER -> location = this.entityReferenceResolver.resolve(filterPreference.getUser(), + EntityType.DOCUMENT); + } + // FIXME: Do we need a new execution context? + ScriptContext currentScriptContext = this.scriptContextManager.getCurrentScriptContext(); + currentScriptContext.setAttribute("location", location, ScriptContext.ENGINE_SCOPE); + return this.templateManager.renderNoException(LOCATION_TEMPLATE); + } + private String displayEventTypes(NotificationFilterPreference filterPreference) throws LiveDataException { StringBuilder result = new StringBuilder("<ul class=\"list-unstyled\">"); @@ -302,6 +319,23 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF result.bindings.putAll(locationHQLQuery.bindings); } } + + case NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> { + // We authorize only a single constraint here + // FIXME: Actually maybe we should allow specifying multiple equals constraints? + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + if (NotificationFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( + constraint.getValue())) { + queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); + } else { + queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put("eventTypes", queryParameter); + } + } + } } } if (!queryWhereClauses.isEmpty()) { @@ -442,6 +476,9 @@ private String handleSortEntries(List<LiveDataQuery.SortEntry> sortEntries) case NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> clauses.add(String.format("nfp.filterType %s", sortOperator)); + + case NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> + clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); } } if (!clauses.isEmpty()) { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index 34229a10509b..5d5b0abfbb27 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -784,6 +784,7 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', #set ($discard = $xwiki.jsx.use('XWiki.Notifications.Code.NotificationsCustomFiltersPreferencesMacro')) ## TODO: replace by $xwiki.sswx.use() or something like this when XWIKI-12788 is closed. #set ($discard = $xwiki.linkx.use($services.webjars.url('bootstrap-switch', 'css/bootstrap3/bootstrap-switch.min.css'), {'type': 'text/css', 'rel': 'stylesheet'})) +#set ($discard = $xwiki.linkx.use($services.webjars.url('org.xwiki.platform:xwiki-platform-notifications-webjar', 'style.css'), {'type': 'text/css', 'rel': 'stylesheet'})) ###################################################### ### MACRO CONTENT ###################################################### diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml index 90f024438731..6025577f9001 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml @@ -54,6 +54,7 @@ notifications.filters.preferences.scopeNotificationFilter.name=Scope Notificatio notifications.filters.preferences.scopeNotificationFilter.page=Page only notifications.filters.preferences.scopeNotificationFilter.space=Page and children notifications.filters.preferences.scopeNotificationFilter.wiki=Wiki +notifications.filters.preferences.scopeNotificationFilter.user=User ## Notifications toggles notifications.toggle.disabled.hint=You need to enable notifications in your settings if you wish to watch these locations diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue index fc1b2282969f..85d5eaefcda6 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerScope.vue @@ -34,8 +34,8 @@ :intercept-touch="false" > <template #viewer> - <XWikiIcon :iconDescriptor="{name: entry.scope.icon}" /> - <span class="scope-name">{{ entry.scope.name }}</span> + <XWikiIcon :iconDescriptor="{name: entry[propertyId].icon}" /> + <span class="scope-name">{{ entry[propertyId].name }}</span> </template> <!-- @@ -57,10 +57,9 @@ export default { mixins: [displayerMixin] } </script> -<style> -.scope-name { +<style scoped> +span.scope-name { margin-left: 1em; } - </style> \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/notification/filters/livedatalocation.vm b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/notification/filters/livedatalocation.vm new file mode 100644 index 000000000000..4db7c2793bf9 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/notification/filters/livedatalocation.vm @@ -0,0 +1,21 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +#template('hierarchy_macros.vm') +#hierarchy($location, {'limit': 5, 'plain': false, 'local': false, 'displayTitle': false}) \ No newline at end of file From c44675c4f6deffe774d98e7a7c83c1664760e47c Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Wed, 6 Mar 2024 17:55:51 +0100 Subject: [PATCH 04/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- ...nFiltersLiveDataConfigurationProvider.java | 7 +-- ...NotificationFiltersLiveDataEntryStore.java | 50 ++++++++++++++++--- ...ficationsCustomFiltersPreferencesMacro.xml | 8 +-- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java index 353f6f203574..47fb48e80a99 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java @@ -66,7 +66,6 @@ public class NotificationFiltersLiveDataConfigurationProvider implements Provide static final String EVENT_TYPES_FIELD = "eventTypes"; static final String NOTIFICATION_FORMATS_FIELD = "notificationFormats"; static final String IS_ENABLED_FIELD = "isEnabled"; - static final String IS_ENABLED_CHECKED_FIELD = "isEnabled_checked"; static final String DOC_VIEWABLE_FIELD = "doc_viewable"; static final String DOC_HAS_DELETE_FIELD = "doc_hasdelete"; private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.custom.table."; @@ -76,7 +75,6 @@ public class NotificationFiltersLiveDataConfigurationProvider implements Provide private static final String VALUE_KEY = "value"; private static final String LABEL_KEY = "label"; - public enum Scope { WIKI("wiki"), @@ -158,7 +156,7 @@ private LiveDataPropertyDescriptor getDisplayDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "name")); descriptor.setId(DISPLAY_FIELD); descriptor.setType(STRING_TYPE); - // FIXME: we should maybe display it by default, as it's what makes custom filter displayer works... + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); descriptor.setVisible(false); descriptor.setEditable(false); descriptor.setSortable(false); @@ -285,6 +283,9 @@ private LiveDataPropertyDescriptor getIsEnabledDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "isEnabled")); descriptor.setId(IS_ENABLED_FIELD); descriptor.setType("Boolean"); + LiveDataPropertyDescriptor.FilterDescriptor filterBoolean = + new LiveDataPropertyDescriptor.FilterDescriptor("boolean"); + descriptor.setFilter(filterBoolean); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("toggle")); descriptor.setVisible(true); descriptor.setEditable(false); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java index ce1f6d9b8fa3..fdbcf51cf2ae 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java @@ -82,6 +82,8 @@ public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore private static final String EMAIL_FORMAT = "email"; private static final String ALERT_FORMAT = "alert"; private static final String LOCATION_TEMPLATE = "notification/filters/livedatalocation.vm"; + private static final String TARGET_SOURCE_PARAMETER = "target"; + private static final String WIKI_SOURCE_PARAMETER = "wiki"; @Inject private NotificationFilterPreferenceStore notificationFilterPreferenceStore; @@ -152,16 +154,29 @@ private Map<String, Object> getPreferenceInformation(NotificationFilterPreferenc this.displayEventTypes(filterPreference), NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, displayNotificationFormats(filterPreference), - NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, filterPreference.isEnabled(), NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope), NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, this.displayLocation(filterPreference, scope), NotificationFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, this.renderDisplay(filterPreference), NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, - this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType()) + this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType()), + NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, displayIsEnabled(filterPreference), + "isEnabled_checked", filterPreference.isEnabled(), + "isEnabled_data", Map.of( + "preferenceId", filterPreference.getId() + ) ); } + private String displayIsEnabled(NotificationFilterPreference filterPreference) + { + String checked = (filterPreference.isEnabled()) ? "checked=\"checked\"" : ""; + String disabled = (filterPreference.getId().startsWith("watchlist_")) ? "disabled=\"disabled\"" : ""; + String html = "<input type=\"checkbox\" class=\"notificationFilterPreferenceCheckbox\" " + + "data-preferenceId=\"%s\" %s %s />"; + return String.format(html, filterPreference.getId(), checked, disabled); + } + private String displayLocation(NotificationFilterPreference filterPreference, NotificationFiltersLiveDataConfigurationProvider.Scope scope) { @@ -293,7 +308,8 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF case NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { // We authorize only a single constraint here LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { NotificationFiltersLiveDataConfigurationProvider.Scope scope = NotificationFiltersLiveDataConfigurationProvider.Scope.valueOf( String.valueOf(constraint.getValue())); @@ -326,7 +342,8 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); if (EQUALS_OPERATOR.equals(constraint.getOperator())) { if (NotificationFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( - constraint.getValue())) { + constraint.getValue())) + { queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); } else { queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); @@ -339,7 +356,7 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF } } if (!queryWhereClauses.isEmpty()) { - StringBuilder stringBuilder = new StringBuilder(" where "); + StringBuilder stringBuilder = new StringBuilder(" and "); Iterator<String> iterator = queryWhereClauses.iterator(); while (iterator.hasNext()) { stringBuilder.append(iterator.next()); @@ -502,16 +519,33 @@ public LiveData get(LiveDataQuery query) throws LiveDataException if (query.getOffset() > Integer.MAX_VALUE) { throw new LiveDataException("Currently only integer offsets are supported."); } + 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)); + EntityReference ownerReference; + if (WIKI_SOURCE_PARAMETER.equals(target)) { + ownerReference = + this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER)), + EntityType.WIKI); + } else { + ownerReference = this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(target)), + EntityType.DOCUMENT); + } + String serializedOwner = this.entityReferenceSerializer.serialize(ownerReference); + LiveData liveData = new LiveData(); - String baseQuery = "select nfp from DefaultNotificationFilterPreference nfp "; + String baseQuery = "select nfp from DefaultNotificationFilterPreference nfp where owner = :owner"; Optional<FiltersHQLQuery> optionalFiltersHQLQuery = handleFilter(query.getFilters()); if (optionalFiltersHQLQuery.isPresent()) { baseQuery += optionalFiltersHQLQuery.get().whereClause; } baseQuery += handleSortEntries(query.getSort()); try { - // FIXME: handle wiki - Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL); + Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL) + .bindValue("owner", serializedOwner) + .setWiki(ownerReference.extractReference(EntityType.WIKI).getName()); if (optionalFiltersHQLQuery.isPresent()) { for (Map.Entry<String, Object> binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index 5d5b0abfbb27..9df0a96525a4 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -801,15 +801,17 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', </div> </div> #set ($sourceParameters = { - 'translationPrefix': 'notifications.settings.filters.preferences.custom.table.', - 'type': 'custom' + 'type': 'custom', + 'target': $wikimacro.parameters.target }) #if ($wikimacro.parameters.target == 'user') #set ($discard = $sourceParameters.put('user', $services.model.serialize($targetUser, 'default'))) + #else + #set ($discard = $sourceParameters.put('wiki', $services.model.serialize($services.wiki.getCurrentWikiReference(), 'default'))) #end $services.liveData.render({ 'id': 'notificationCustomFilterPreferencesLiveData', - 'properties': 'scope,location,filterType,eventTypes,notificationFormats,isEnabled', + 'properties': 'display,scope,location,filterType,eventTypes,notificationFormats,isEnabled', 'source': 'notificationFilters', 'sourceParameters': $escapetool.url($sourceParameters), 'limit': 10 From 8104f94338f79765c63e314d4e604a0a4c5137e4 Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Wed, 6 Mar 2024 18:17:03 +0100 Subject: [PATCH 05/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- ...nFiltersLiveDataConfigurationProvider.java | 36 ++++++++----------- ...NotificationFiltersLiveDataEntryStore.java | 12 +++++-- ...ficationsCustomFiltersPreferencesMacro.xml | 2 +- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java index 47fb48e80a99..9549f5573712 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java @@ -20,8 +20,6 @@ package org.xwiki.notifications.filters.internal.livedata; import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -34,7 +32,9 @@ import org.slf4j.Logger; import org.xwiki.component.annotation.Component; +import org.xwiki.icon.IconException; import org.xwiki.icon.IconManager; +import org.xwiki.livedata.LiveDataActionDescriptor; import org.xwiki.livedata.LiveDataConfiguration; import org.xwiki.livedata.LiveDataEntryDescriptor; import org.xwiki.livedata.LiveDataException; @@ -44,8 +44,6 @@ import org.xwiki.localization.ContextualLocalizationManager; import org.xwiki.notifications.filters.NotificationFilterType; -import com.xpn.xwiki.objects.classes.LevelsClass; - /** * Configuration of the {@link NotificationFiltersLiveDataSource}. * @@ -66,10 +64,10 @@ public class NotificationFiltersLiveDataConfigurationProvider implements Provide static final String EVENT_TYPES_FIELD = "eventTypes"; static final String NOTIFICATION_FORMATS_FIELD = "notificationFormats"; static final String IS_ENABLED_FIELD = "isEnabled"; - static final String DOC_VIEWABLE_FIELD = "doc_viewable"; + static final String ACTIONS_FIELD = "actions"; 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 REMOVE = "remove"; + private static final String DELETE = "delete"; private static final String STRING_TYPE = "String"; private static final String HTML_DISPLAYER = "html"; private static final String VALUE_KEY = "value"; @@ -122,20 +120,16 @@ public LiveDataConfiguration get() entryDescriptor.setIdProperty(ID_FIELD); meta.setEntryDescriptor(entryDescriptor); - // FIXME: Handle delete action - /* - LiveDataActionDescriptor removeAction = new LiveDataActionDescriptor(); - removeAction.setName(this.l10n.getTranslationPlain("wordsNotification.settings.remove")); - removeAction.setId(REMOVE); - removeAction.setAllowProperty(DOC_HAS_DELETE_FIELD); - removeAction.setUrlProperty(REMOVE_OBJECT_URL_FIELD); + LiveDataActionDescriptor deleteAction = new LiveDataActionDescriptor(); + deleteAction.setName(this.l10n.getTranslationPlain("liveData.action.delete")); + deleteAction.setId(DELETE); + deleteAction.setAllowProperty(DOC_HAS_DELETE_FIELD); try { - removeAction.setIcon(this.iconManager.getMetaData(REMOVE)); + deleteAction.setIcon(this.iconManager.getMetaData(DELETE)); } catch (IconException e) { this.logger.error("Error while getting icon for the remove action", e); } - meta.setActions(List.of(removeAction)); - */ + meta.setActions(List.of(deleteAction)); meta.setPropertyDescriptors(List.of( getDisplayDescriptor(), @@ -144,7 +138,8 @@ public LiveDataConfiguration get() getFilterTypeDescriptor(), getNotificationFormatsDescriptor(), getEventTypesDescriptor(), - getIsEnabledDescriptor() + getIsEnabledDescriptor(), + getActionDescriptor() )); return input; @@ -295,20 +290,19 @@ private LiveDataPropertyDescriptor getIsEnabledDescriptor() return descriptor; } - /* private LiveDataPropertyDescriptor getActionDescriptor() { LiveDataPropertyDescriptor actionDescriptor = new LiveDataPropertyDescriptor(); - actionDescriptor.setName(this.l10n.getTranslationPlain("wordsNotification.livedata.action")); + actionDescriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "_actions")); actionDescriptor.setId(ACTIONS_FIELD); LiveDataPropertyDescriptor.DisplayerDescriptor displayer = new LiveDataPropertyDescriptor.DisplayerDescriptor(ACTIONS_FIELD); - displayer.setParameter(ACTIONS_FIELD, List.of(REMOVE)); + displayer.setParameter(ACTIONS_FIELD, List.of(DELETE)); actionDescriptor.setDisplayer(displayer); actionDescriptor.setVisible(true); actionDescriptor.setEditable(false); actionDescriptor.setSortable(false); actionDescriptor.setFilterable(false); return actionDescriptor; - }*/ + } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java index fdbcf51cf2ae..d204d5f33ae9 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java @@ -20,6 +20,7 @@ package org.xwiki.notifications.filters.internal.livedata; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -130,6 +131,7 @@ public Optional<Map<String, Object>> get(Object entryId) throws LiveDataExceptio filterPreferenceOpt = notificationFilterPreferenceStore .getFilterPreference(String.valueOf(entryId), wikiReference); if (filterPreferenceOpt.isPresent()) { + // FIXME: check authorization NotificationFilterPreference filterPreference = filterPreferenceOpt.get(); result = Optional.of(getPreferenceInformation(filterPreference)); } @@ -147,8 +149,10 @@ private Map<String, Object> getPreferenceInformation(NotificationFilterPreferenc { NotificationFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); + HashMap<String, Object> result = new LinkedHashMap<>(); // FIXME: Check authorization? - return Map.of( + // FIXME: refactor to use plenty of puts: Map.of only accept 10 args + result.putAll(Map.of( NotificationFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId(), NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, this.displayEventTypes(filterPreference), @@ -165,7 +169,10 @@ NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, displayIsEnab "isEnabled_data", Map.of( "preferenceId", filterPreference.getId() ) - ); + )); + // We don't care: if we access the LD we do have delete. + result.put(NotificationFiltersLiveDataConfigurationProvider.DOC_HAS_DELETE_FIELD, true); + return result; } private String displayIsEnabled(NotificationFilterPreference filterPreference) @@ -533,6 +540,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException ownerReference = this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(target)), EntityType.DOCUMENT); } + // FIXME: check authorization to access this owner info String serializedOwner = this.entityReferenceSerializer.serialize(ownerReference); LiveData liveData = new LiveData(); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index 9df0a96525a4..019c9a1ab143 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -811,7 +811,7 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', #end $services.liveData.render({ 'id': 'notificationCustomFilterPreferencesLiveData', - 'properties': 'display,scope,location,filterType,eventTypes,notificationFormats,isEnabled', + 'properties': 'display,scope,location,filterType,eventTypes,notificationFormats,isEnabled,actions', 'source': 'notificationFilters', 'sourceParameters': $escapetool.url($sourceParameters), 'limit': 10 From 9141efc033c9c88badea2e5d8256a75f0eb05917 Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Thu, 7 Mar 2024 11:02:03 +0100 Subject: [PATCH 06/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- ...cationFilterLiveDataTranslationHelper.java | 6 + ...nFiltersLiveDataConfigurationProvider.java | 47 ++++--- ...NotificationFiltersLiveDataEntryStore.java | 117 ++++++++---------- .../NotificationFiltersLiveDataSource.java | 8 +- 4 files changed, 86 insertions(+), 92 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index 3da37b20624d..604b035ed659 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -34,6 +34,7 @@ import org.xwiki.eventstream.RecordableEventDescriptorManager; import org.xwiki.livedata.LiveDataException; import org.xwiki.localization.ContextualLocalizationManager; +import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilterType; @Component(roles = NotificationFilterLiveDataTranslationHelper.class) @@ -88,6 +89,11 @@ public String getEventTypeTranslation(String eventType) throws LiveDataException } } + public String getFormatTranslation(NotificationFormat format) + { + return getTranslationWithPrefix("notifications.format.", format.name().toLowerCase()); + } + public List<Map<String, String>> getAllEventTypesOptions(boolean allFarm) throws LiveDataException { try { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java index 9549f5573712..baa9c909b1de 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java @@ -42,6 +42,7 @@ import org.xwiki.livedata.LiveDataPaginationConfiguration; import org.xwiki.livedata.LiveDataPropertyDescriptor; import org.xwiki.localization.ContextualLocalizationManager; +import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilterType; /** @@ -58,7 +59,6 @@ public class NotificationFiltersLiveDataConfigurationProvider implements Provide static final String ID_FIELD = "filterPreferenceId"; static final String SCOPE_FIELD = "scope"; static final String LOCATION_FIELD = "location"; - // FIXME: Should we keep that? static final String DISPLAY_FIELD = "display"; static final String FILTER_TYPE_FIELD = "filterType"; static final String EVENT_TYPES_FIELD = "eventTypes"; @@ -112,7 +112,6 @@ public LiveDataConfiguration get() input.setMeta(meta); LiveDataPaginationConfiguration pagination = new LiveDataPaginationConfiguration(); - // FIXME: probably not for system filters? pagination.setShowPageSizeDropdown(true); meta.setPagination(pagination); @@ -160,6 +159,17 @@ private LiveDataPropertyDescriptor getDisplayDescriptor() return descriptor; } + private LiveDataPropertyDescriptor.FilterDescriptor createFilterList(List<Map<String, String>> options) + { + LiveDataPropertyDescriptor.FilterDescriptor filterList = + new LiveDataPropertyDescriptor.FilterDescriptor("list"); + filterList.addOperator("empty", null); + filterList.setParameter("options", options); + filterList.addOperator("equals", null); + filterList.setDefaultOperator("equals"); + return filterList; + } + private LiveDataPropertyDescriptor getScopeDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); @@ -167,17 +177,11 @@ private LiveDataPropertyDescriptor getScopeDescriptor() descriptor.setId(SCOPE_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("scope")); - LiveDataPropertyDescriptor.FilterDescriptor filterList = - new LiveDataPropertyDescriptor.FilterDescriptor("list"); - filterList.addOperator("empty", null); - filterList.setParameter("options", Stream.of(Scope.values()) + descriptor.setFilter(createFilterList(Stream.of(Scope.values()) .map(item -> Map.of( "value", item.name(), "label", this.translationHelper.getScopeTranslation(item) - )).collect(Collectors.toList())); - filterList.addOperator("equals", null); - filterList.setDefaultOperator("equals"); - descriptor.setFilter(filterList); + )).collect(Collectors.toList()))); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -207,17 +211,11 @@ private LiveDataPropertyDescriptor getFilterTypeDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "filterType")); descriptor.setId(FILTER_TYPE_FIELD); descriptor.setType(STRING_TYPE); - LiveDataPropertyDescriptor.FilterDescriptor filterList = - new LiveDataPropertyDescriptor.FilterDescriptor("list"); - filterList.addOperator("empty", null); - filterList.setParameter("options", Stream.of(NotificationFilterType.values()) + descriptor.setFilter(createFilterList(Stream.of(NotificationFilterType.values()) .map(item -> Map.of( "value", item.name(), "label", this.translationHelper.getFilterTypeTranslation(item) - )).collect(Collectors.toList())); - filterList.addOperator("equals", null); - filterList.setDefaultOperator("equals"); - descriptor.setFilter(filterList); + )).collect(Collectors.toList()))); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -233,6 +231,11 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() descriptor.setId(NOTIFICATION_FORMATS_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("html")); + descriptor.setFilter(createFilterList(Stream.of(NotificationFormat.values()).map(item -> + Map.of( + "value", item.name(), + "label", this.translationHelper.getFormatTranslation(item) + )).collect(Collectors.toList()))); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -248,9 +251,6 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor() descriptor.setId(EVENT_TYPES_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); - LiveDataPropertyDescriptor.FilterDescriptor filterList = - new LiveDataPropertyDescriptor.FilterDescriptor("list"); - filterList.addOperator("empty", null); List<Map<String, String>> options = new ArrayList<>(); options.add(Map.of( VALUE_KEY, ALL_EVENTS_OPTION_VALUE, @@ -260,10 +260,7 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor() } catch (LiveDataException e) { this.logger.error("Cannot provide event filter options", e); } - filterList.setParameter("options", options); - filterList.addOperator("equals", null); - filterList.setDefaultOperator("equals"); - descriptor.setFilter(filterList); + descriptor.setFilter(createFilterList(options)); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java index d204d5f33ae9..8f736cdcdbd0 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java @@ -52,6 +52,7 @@ import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.NotificationFilterPreference; import org.xwiki.notifications.filters.NotificationFilterType; +import org.xwiki.notifications.filters.internal.DefaultNotificationFilterPreference; import org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore; import org.xwiki.query.Query; import org.xwiki.query.QueryException; @@ -62,6 +63,8 @@ import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; import org.xwiki.rendering.renderer.printer.WikiPrinter; import org.xwiki.script.ScriptContextManager; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; import org.xwiki.template.TemplateManager; import com.xpn.xwiki.XWikiContext; @@ -85,6 +88,7 @@ public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore private static final String LOCATION_TEMPLATE = "notification/filters/livedatalocation.vm"; 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 private NotificationFilterPreferenceStore notificationFilterPreferenceStore; @@ -121,19 +125,29 @@ public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore @Inject private ScriptContextManager scriptContextManager; + @Inject + private ContextualAuthorizationManager contextualAuthorizationManager; + @Override public Optional<Map<String, Object>> get(Object entryId) throws LiveDataException { Optional<Map<String, Object>> result = Optional.empty(); - WikiReference wikiReference = contextProvider.get().getWikiReference(); + XWikiContext context = contextProvider.get(); + WikiReference wikiReference = context.getWikiReference(); Optional<NotificationFilterPreference> filterPreferenceOpt; try { filterPreferenceOpt = notificationFilterPreferenceStore .getFilterPreference(String.valueOf(entryId), wikiReference); if (filterPreferenceOpt.isPresent()) { - // FIXME: check authorization - NotificationFilterPreference filterPreference = filterPreferenceOpt.get(); - result = Optional.of(getPreferenceInformation(filterPreference)); + DefaultNotificationFilterPreference filterPreference = + (DefaultNotificationFilterPreference) filterPreferenceOpt.get(); + if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN) + || filterPreference.getOwner().equals( + this.entityReferenceSerializer.serialize(context.getUserReference()))) { + result = Optional.of(getPreferenceInformation(filterPreference)); + } else { + throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); + } } } catch (NotificationException e) { throw new LiveDataException( @@ -150,25 +164,24 @@ private Map<String, Object> getPreferenceInformation(NotificationFilterPreferenc NotificationFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); HashMap<String, Object> result = new LinkedHashMap<>(); - // FIXME: Check authorization? - // FIXME: refactor to use plenty of puts: Map.of only accept 10 args - result.putAll(Map.of( - NotificationFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId(), - NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, - this.displayEventTypes(filterPreference), - NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, - displayNotificationFormats(filterPreference), - NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope), - NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, - this.displayLocation(filterPreference, scope), - NotificationFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, this.renderDisplay(filterPreference), - NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, - this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType()), - NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, displayIsEnabled(filterPreference), - "isEnabled_checked", filterPreference.isEnabled(), - "isEnabled_data", Map.of( - "preferenceId", filterPreference.getId() - ) + // Map.of only accept 10 args + result.put(NotificationFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId()); + result.put(NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, + this.displayEventTypes(filterPreference)); + result.put(NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, + displayNotificationFormats(filterPreference)); + result.put(NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope)); + result.put(NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, + this.displayLocation(filterPreference, scope)); + result.put(NotificationFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, + this.renderDisplay(filterPreference)); + result.put(NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, + this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType())); + result.put(NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, + displayIsEnabled(filterPreference)); + result.put("isEnabled_checked", filterPreference.isEnabled()); + result.put("isEnabled_data", Map.of( + "preferenceId", filterPreference.getId() )); // We don't care: if we access the LD we do have delete. result.put(NotificationFiltersLiveDataConfigurationProvider.DOC_HAS_DELETE_FIELD, true); @@ -224,12 +237,10 @@ private String displayEventTypes(NotificationFilterPreference filterPreference) private String displayNotificationFormats(NotificationFilterPreference filterPreference) { StringBuilder result = new StringBuilder("<ul class=\"list-unstyled\">"); - String translationPrefix = "notifications.format."; for (NotificationFormat notificationFormat : filterPreference.getNotificationFormats()) { result.append("<li>"); - result.append(this.translationHelper.getTranslationWithPrefix(translationPrefix, - notificationFormat.name().toLowerCase())); + result.append(this.translationHelper.getFormatTranslation(notificationFormat)); result.append("</li>"); } result.append("</ul>"); @@ -309,7 +320,18 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF } case NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { - handleFormatFilter(queryFilter, queryWhereClauses); + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + String constraintValue = String.valueOf(constraint.getValue()); + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isEmpty(constraintValue)) { + if (NotificationFormat.ALERT.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.alertEnabled = 1"); + } + if (NotificationFormat.EMAIL.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.emailEnabled = 1"); + } + } } case NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { @@ -434,41 +456,6 @@ private String buildQueryClause(LiveDataQuery.Filter filter, List<String> clause return result.toString(); } - private void handleFormatFilter(LiveDataQuery.Filter formatFilter, List<String> queryWhereClauses) - { - List<String> clauses = new ArrayList<>(); - for (LiveDataQuery.Constraint constraint : formatFilter.getConstraints()) { - boolean isEmailEnabled = false; - boolean isAlertEnabled = false; - String constraintValue = String.valueOf(constraint.getValue()); - if (StringUtils.isNotBlank(constraintValue)) { - if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { - isEmailEnabled = EMAIL_FORMAT.startsWith(constraintValue); - isAlertEnabled = ALERT_FORMAT.startsWith(constraintValue); - } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { - isEmailEnabled = EMAIL_FORMAT.contains(constraintValue); - isAlertEnabled = ALERT_FORMAT.contains(constraintValue); - } else if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - isEmailEnabled = EMAIL_FORMAT.equals(constraintValue); - isAlertEnabled = ALERT_FORMAT.equals(constraintValue); - } - if (isEmailEnabled) { - clauses.add("nfp.emailEnabled = 1"); - } else { - clauses.add("nfp.emailEnabled = 0"); - } - if (isAlertEnabled) { - clauses.add("nfp.alertEnabled = 1"); - } else { - clauses.add("nfp.alertEnabled = 0"); - } - } - } - if (!clauses.isEmpty()) { - queryWhereClauses.add(buildQueryClause(formatFilter, clauses)); - } - } - private String handleSortEntries(List<LiveDataQuery.SortEntry> sortEntries) { List<String> clauses = new ArrayList<>(); @@ -540,7 +527,11 @@ public LiveData get(LiveDataQuery query) throws LiveDataException ownerReference = this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(target)), EntityType.DOCUMENT); } - // FIXME: check authorization to access this owner info + XWikiContext context = this.contextProvider.get(); + if (!this.contextualAuthorizationManager.hasAccess(Right.ADMIN) + && !ownerReference.equals(context.getUserReference())) { + throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); + } String serializedOwner = this.entityReferenceSerializer.serialize(ownerReference); LiveData liveData = new LiveData(); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java index 8e02c0ee63b5..968bbc521d99 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java @@ -38,21 +38,21 @@ public class NotificationFiltersLiveDataSource implements LiveDataSource @Inject @Named(NAME) - private LiveDataEntryStore wordsQueryLiveDataEntryStore; + private LiveDataEntryStore notificationFiltersLiveDataEntryStore; @Inject @Named(NAME) - private LiveDataPropertyDescriptorStore wordsQueryLiveDataPropertyDescriptorStore; + private LiveDataPropertyDescriptorStore notificationFiltersLiveDataPropertyDescriptorStore; @Override public LiveDataEntryStore getEntries() { - return this.wordsQueryLiveDataEntryStore; + return this.notificationFiltersLiveDataEntryStore; } @Override public LiveDataPropertyDescriptorStore getProperties() { - return this.wordsQueryLiveDataPropertyDescriptorStore; + return this.notificationFiltersLiveDataPropertyDescriptorStore; } } From aa712377d5ed6ffcd36ed419d3bfb23f10dd35ee Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Thu, 7 Mar 2024 16:23:41 +0100 Subject: [PATCH 07/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- .../filters/NotificationFilterManager.java | 14 ++ .../DefaultNotificationFilterManager.java | 30 +++ .../DefaultFilterPreferencesModelBridge.java | 13 +- ...leFilterPreferenceDocumentInitializer.java | 13 +- ...cationFilterLiveDataTranslationHelper.java | 5 +- ...FiltersLiveDataConfigurationProvider.java} | 21 +- ...FiltersLiveDataConfigurationResolver.java} | 8 +- ...ationCustomFiltersLiveDataEntryStore.java} | 75 ++++--- ...ltersLiveDataPropertyDescriptorStore.java} | 10 +- ...ificationCustomFiltersLiveDataSource.java} | 9 +- ...mFiltersLiveDataConfigurationProvider.java | 169 +++++++++++++++ ...mFiltersLiveDataConfigurationResolver.java | 53 +++++ ...cationSystemFiltersLiveDataEntryStore.java | 204 ++++++++++++++++++ ...iltersLiveDataPropertyDescriptorStore.java | 54 +++++ ...tificationSystemFiltersLiveDataSource.java | 57 +++++ .../main/resources/META-INF/components.txt | 15 +- ...ficationsCustomFiltersPreferencesMacro.xml | 3 +- ...ficationsSystemFiltersPreferencesMacro.xml | 37 +--- .../XWiki/Notifications/Code/Translations.xml | 37 +++- 19 files changed, 706 insertions(+), 121 deletions(-) rename xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/{NotificationFiltersLiveDataConfigurationProvider.java => custom/NotificationCustomFiltersLiveDataConfigurationProvider.java} (93%) rename xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/{NotificationFiltersLiveDataConfigurationResolver.java => custom/NotificationCustomFiltersLiveDataConfigurationResolver.java} (87%) rename xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/{NotificationFiltersLiveDataEntryStore.java => custom/NotificationCustomFiltersLiveDataEntryStore.java} (87%) rename xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/{NotificationFiltersLiveDataPropertyDescriptorStore.java => custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java} (81%) rename xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/{NotificationFiltersLiveDataSource.java => custom/NotificationCustomFiltersLiveDataSource.java} (86%) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java index 0299d9a859eb..b0b225ba301f 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -31,6 +32,7 @@ import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; import org.xwiki.notifications.preferences.NotificationPreference; import org.xwiki.rendering.block.Block; +import org.xwiki.user.UserReference; /** * Provide an interface for interacting with user notification filters. @@ -109,6 +111,18 @@ Stream<NotificationFilter> getFiltersRelatedToNotificationPreference(Collection< */ Stream<NotificationFilter> getToggleableFilters(Collection<NotificationFilter> filters); + default List<ToggleableNotificationFilter> getToggleableFilters(UserReference userReference) + throws NotificationException + { + return List.of(); + } + + default List<ToggleableNotificationFilter> getToggleableFilters(WikiReference wikiReference) + throws NotificationException + { + return List.of(); + } + /** * For all toggleable notification filters, get if the filter is enabled regarding the user profile. * diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java index 49479ed780bb..fab361814da2 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java @@ -19,6 +19,7 @@ */ package org.xwiki.notifications.filters.internal; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -46,6 +47,8 @@ import org.xwiki.notifications.filters.NotificationFilterPreference; import org.xwiki.notifications.preferences.NotificationPreference; import org.xwiki.rendering.block.Block; +import org.xwiki.user.UserReference; +import org.xwiki.user.UserReferenceSerializer; import org.xwiki.wiki.descriptor.WikiDescriptorManager; /** @@ -76,6 +79,10 @@ public class DefaultNotificationFilterManager implements NotificationFilterManag @Named("cached") private FilterPreferencesModelBridge filterPreferencesModelBridge; + @Inject + @Named("document") + private UserReferenceSerializer<DocumentReference> documentReferenceUserReferenceSerializer; + @Override public Collection<NotificationFilter> getAllFilters(boolean allWikis) throws NotificationException { @@ -207,6 +214,29 @@ public Stream<NotificationFilter> getToggleableFilters(Collection<NotificationFi return filters.stream().filter(filter -> filter instanceof ToggleableNotificationFilter); } + @Override + public List<ToggleableNotificationFilter> getToggleableFilters(UserReference userReference) + throws NotificationException + { + DocumentReference userDoc = this.documentReferenceUserReferenceSerializer.serialize(userReference); + List<ToggleableNotificationFilter> result = new ArrayList<>(); + getAllFilters(userDoc, false) + .stream().filter(filter -> filter instanceof ToggleableNotificationFilter) + .forEach(item -> result.add((ToggleableNotificationFilter) item)); + return result; + } + + @Override + public List<ToggleableNotificationFilter> getToggleableFilters(WikiReference wikiReference) + throws NotificationException + { + List<ToggleableNotificationFilter> result = new ArrayList<>(); + getAllFilters(wikiReference) + .stream().filter(filter -> filter instanceof ToggleableNotificationFilter) + .forEach(item -> result.add((ToggleableNotificationFilter) item)); + return result; + } + @Override public Map<String, Boolean> getToggeableFilterActivations(DocumentReference user) throws NotificationException diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java index 4d4ff989d456..56459f333601 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java @@ -63,13 +63,6 @@ @Singleton public class DefaultFilterPreferencesModelBridge implements FilterPreferencesModelBridge { - private static final LocalDocumentReference TOGGLEABLE_FILTER_PREFERENCE_CLASS = - new LocalDocumentReference(Arrays.asList("XWiki", "Notifications", "Code"), "ToggleableFilterPreferenceClass"); - - private static final String FIELD_FILTER_NAME = "filterName"; - - private static final String FIELD_IS_ENABLED = "isEnabled"; - @Inject private Provider<XWikiContext> contextProvider; @@ -120,10 +113,12 @@ public Map<String, Boolean> getToggleableFilterActivations(DocumentReference use if (filter instanceof ToggleableNotificationFilter) { ToggleableNotificationFilter toggleableFilter = (ToggleableNotificationFilter) filter; boolean status = toggleableFilter.isEnabledByDefault(); - BaseObject obj = doc.getXObject(TOGGLEABLE_FILTER_PREFERENCE_CLASS, FIELD_FILTER_NAME, + BaseObject obj = doc.getXObject(ToggleableFilterPreferenceDocumentInitializer.XCLASS, + ToggleableFilterPreferenceDocumentInitializer.FIELD_FILTER_NAME, filter.getName(), false); if (obj != null) { - status = obj.getIntValue(FIELD_IS_ENABLED, status ? 1 : 0) != 0; + status = obj.getIntValue(ToggleableFilterPreferenceDocumentInitializer.FIELD_IS_ENABLED, + status ? 1 : 0) != 0; } filterStatus.put(filter.getName(), status); } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java index 1efda3b7d72a..c5566e6d401b 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java @@ -47,19 +47,26 @@ public class ToggleableFilterPreferenceDocumentInitializer extends AbstractManda * The path to the class parent document. */ private static final List<String> PARENT_PATH = Arrays.asList("XWiki", "Notifications", "Code"); + public static final LocalDocumentReference XCLASS = + new LocalDocumentReference(PARENT_PATH, "ToggleableFilterPreferenceClass"); + + public static final String FIELD_FILTER_NAME = "filterName"; + + public static final String FIELD_IS_ENABLED = "isEnabled"; + /** * Default constructor. */ public ToggleableFilterPreferenceDocumentInitializer() { - super(new LocalDocumentReference(PARENT_PATH, "ToggleableFilterPreferenceClass")); + super(XCLASS); } @Override protected void createClass(BaseClass xclass) { - xclass.addTextField("filterName", "Filter name", 64); - xclass.addBooleanField("isEnabled", "Is enabled ?", "checkbox", "", true); + xclass.addTextField(FIELD_FILTER_NAME, "Filter name", 64); + xclass.addBooleanField(FIELD_IS_ENABLED, "Is enabled ?", "checkbox", "", true); } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index 604b035ed659..8d6654d929da 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -17,12 +17,10 @@ * 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.List; import java.util.Map; -import java.util.stream.Collector; import java.util.stream.Collectors; import javax.inject.Inject; @@ -36,6 +34,7 @@ import org.xwiki.localization.ContextualLocalizationManager; import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilterType; +import org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataConfigurationProvider; @Component(roles = NotificationFilterLiveDataTranslationHelper.class) @Singleton @@ -66,7 +65,7 @@ public String getFilterTypeTranslation(NotificationFilterType filterType) return getTranslationWithPrefix("notifications.filters.type.custom.", filterType.name().toLowerCase()); } - public String getScopeTranslation(NotificationFiltersLiveDataConfigurationProvider.Scope scope) + public String getScopeTranslation(NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { return getTranslationWithPrefix("notifications.filters.preferences.scopeNotificationFilter.", scope.name().toLowerCase()); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java similarity index 93% rename from xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java rename to xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java index baa9c909b1de..72ed59298ead 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java @@ -17,7 +17,7 @@ * 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; +package org.xwiki.notifications.filters.internal.livedata.custom; import java.util.ArrayList; import java.util.List; @@ -44,16 +44,17 @@ import org.xwiki.localization.ContextualLocalizationManager; import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilterType; +import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; /** - * Configuration of the {@link NotificationFiltersLiveDataSource}. + * Configuration of the {@link NotificationCustomFiltersLiveDataSource}. * * @version $Id$ */ @Component @Singleton -@Named(NotificationFiltersLiveDataSource.NAME) -public class NotificationFiltersLiveDataConfigurationProvider implements Provider<LiveDataConfiguration> +@Named(NotificationCustomFiltersLiveDataSource.NAME) +public class NotificationCustomFiltersLiveDataConfigurationProvider implements Provider<LiveDataConfiguration> { public static final String ALL_EVENTS_OPTION_VALUE = "__ALL_EVENTS__"; static final String ID_FIELD = "filterPreferenceId"; @@ -179,8 +180,8 @@ private LiveDataPropertyDescriptor getScopeDescriptor() descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("scope")); descriptor.setFilter(createFilterList(Stream.of(Scope.values()) .map(item -> Map.of( - "value", item.name(), - "label", this.translationHelper.getScopeTranslation(item) + VALUE_KEY, item.name(), + LABEL_KEY, this.translationHelper.getScopeTranslation(item) )).collect(Collectors.toList()))); descriptor.setVisible(true); descriptor.setEditable(false); @@ -213,8 +214,8 @@ private LiveDataPropertyDescriptor getFilterTypeDescriptor() descriptor.setType(STRING_TYPE); descriptor.setFilter(createFilterList(Stream.of(NotificationFilterType.values()) .map(item -> Map.of( - "value", item.name(), - "label", this.translationHelper.getFilterTypeTranslation(item) + VALUE_KEY, item.name(), + LABEL_KEY, this.translationHelper.getFilterTypeTranslation(item) )).collect(Collectors.toList()))); descriptor.setVisible(true); descriptor.setEditable(false); @@ -233,8 +234,8 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("html")); descriptor.setFilter(createFilterList(Stream.of(NotificationFormat.values()).map(item -> Map.of( - "value", item.name(), - "label", this.translationHelper.getFormatTranslation(item) + VALUE_KEY, item.name(), + LABEL_KEY, this.translationHelper.getFormatTranslation(item) )).collect(Collectors.toList()))); descriptor.setVisible(true); descriptor.setEditable(false); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationResolver.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationResolver.java similarity index 87% rename from xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationResolver.java rename to xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationResolver.java index da5fec521736..ef7de1f3ee7f 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataConfigurationResolver.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationResolver.java @@ -17,7 +17,7 @@ * 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; +package org.xwiki.notifications.filters.internal.livedata.custom; import javax.inject.Inject; import javax.inject.Named; @@ -37,12 +37,12 @@ */ @Component @Singleton -@Named(NotificationFiltersLiveDataSource.NAME) -public class NotificationFiltersLiveDataConfigurationResolver implements +@Named(NotificationCustomFiltersLiveDataSource.NAME) +public class NotificationCustomFiltersLiveDataConfigurationResolver implements LiveDataConfigurationResolver<LiveDataConfiguration> { @Inject - @Named(NotificationFiltersLiveDataSource.NAME) + @Named(NotificationCustomFiltersLiveDataSource.NAME) private Provider<LiveDataConfiguration> notificationFiltersLiveDataConfigurationProvider; @Override diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java similarity index 87% rename from xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java rename to xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index 8f736cdcdbd0..dd6bc4c3c722 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -17,7 +17,7 @@ * 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; +package org.xwiki.notifications.filters.internal.livedata.custom; import java.util.ArrayList; import java.util.HashMap; @@ -54,6 +54,7 @@ import org.xwiki.notifications.filters.NotificationFilterType; import org.xwiki.notifications.filters.internal.DefaultNotificationFilterPreference; import org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore; +import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.query.QueryManager; @@ -70,21 +71,19 @@ import com.xpn.xwiki.XWikiContext; /** - * Dedicated {@link LiveDataEntryStore} for the {@link NotificationFiltersLiveDataSource}. + * Dedicated {@link LiveDataEntryStore} for the {@link NotificationCustomFiltersLiveDataSource}. * This component is in charge of performing the actual HQL queries to display the live data. * * @version $Id$ */ @Component @Singleton -@Named(NotificationFiltersLiveDataSource.NAME) -public class NotificationFiltersLiveDataEntryStore implements LiveDataEntryStore +@Named(NotificationCustomFiltersLiveDataSource.NAME) +public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntryStore { private static final String STARTS_WITH_OPERATOR = "startsWith"; private static final String CONTAINS_OPERATOR = "contains"; private static final String EQUALS_OPERATOR = "equals"; - private static final String EMAIL_FORMAT = "email"; - private static final String ALERT_FORMAT = "alert"; private static final String LOCATION_TEMPLATE = "notification/filters/livedatalocation.vm"; private static final String TARGET_SOURCE_PARAMETER = "target"; private static final String WIKI_SOURCE_PARAMETER = "wiki"; @@ -161,30 +160,30 @@ public Optional<Map<String, Object>> get(Object entryId) throws LiveDataExceptio private Map<String, Object> getPreferenceInformation(NotificationFilterPreference filterPreference) throws NotificationException, LiveDataException { - NotificationFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); + NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); HashMap<String, Object> result = new LinkedHashMap<>(); // Map.of only accept 10 args - result.put(NotificationFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId()); - result.put(NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId()); + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, this.displayEventTypes(filterPreference)); - result.put(NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, displayNotificationFormats(filterPreference)); - result.put(NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope)); - result.put(NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope)); + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, this.displayLocation(filterPreference, scope)); - result.put(NotificationFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.DISPLAY_FIELD, this.renderDisplay(filterPreference)); - result.put(NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType())); - result.put(NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, displayIsEnabled(filterPreference)); result.put("isEnabled_checked", filterPreference.isEnabled()); result.put("isEnabled_data", Map.of( "preferenceId", filterPreference.getId() )); // We don't care: if we access the LD we do have delete. - result.put(NotificationFiltersLiveDataConfigurationProvider.DOC_HAS_DELETE_FIELD, true); + result.put(NotificationCustomFiltersLiveDataConfigurationProvider.DOC_HAS_DELETE_FIELD, true); return result; } @@ -198,7 +197,7 @@ private String displayIsEnabled(NotificationFilterPreference filterPreference) } private String displayLocation(NotificationFilterPreference filterPreference, - NotificationFiltersLiveDataConfigurationProvider.Scope scope) + NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { EntityReference location = null; switch (scope) { @@ -248,7 +247,7 @@ private String displayNotificationFormats(NotificationFilterPreference filterPre return result.toString(); } - private Map<String, String> getScopeInfo(NotificationFiltersLiveDataConfigurationProvider.Scope scope) + private Map<String, String> getScopeInfo(NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { String icon = ""; switch (scope) { @@ -283,17 +282,17 @@ private String renderDisplay(NotificationFilterPreference filterPreference) thro return result; } - private NotificationFiltersLiveDataConfigurationProvider.Scope getScope(NotificationFilterPreference + private NotificationCustomFiltersLiveDataConfigurationProvider.Scope getScope(NotificationFilterPreference filterPreference) { if (!StringUtils.isBlank(filterPreference.getUser())) { - return NotificationFiltersLiveDataConfigurationProvider.Scope.USER; + return NotificationCustomFiltersLiveDataConfigurationProvider.Scope.USER; } else if (!StringUtils.isBlank(filterPreference.getPageOnly())) { - return NotificationFiltersLiveDataConfigurationProvider.Scope.PAGE; + return NotificationCustomFiltersLiveDataConfigurationProvider.Scope.PAGE; } else if (!StringUtils.isBlank(filterPreference.getPage())) { - return NotificationFiltersLiveDataConfigurationProvider.Scope.SPACE; + return NotificationCustomFiltersLiveDataConfigurationProvider.Scope.SPACE; } else { - return NotificationFiltersLiveDataConfigurationProvider.Scope.WIKI; + return NotificationCustomFiltersLiveDataConfigurationProvider.Scope.WIKI; } } @@ -309,7 +308,7 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF List<String> queryWhereClauses = new ArrayList<>(); for (LiveDataQuery.Filter queryFilter : queryFilters) { switch (queryFilter.getProperty()) { - case NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> { // We only check first constraint: if there's more they are either redundant or contradictory. LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { @@ -319,7 +318,7 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF } } - case NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { // We authorize only a single constraint here LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); String constraintValue = String.valueOf(constraint.getValue()); @@ -334,19 +333,19 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF } } - case NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { // We authorize only a single constraint here LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); if (EQUALS_OPERATOR.equals(constraint.getOperator()) && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { - NotificationFiltersLiveDataConfigurationProvider.Scope scope = - NotificationFiltersLiveDataConfigurationProvider.Scope.valueOf( + NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = + NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( String.valueOf(constraint.getValue())); queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); } } - case NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> { // We authorize only a single constraint here LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); if (EQUALS_OPERATOR.equals(constraint.getOperator())) { @@ -356,7 +355,7 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF } } - case NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> { Optional<FiltersHQLQuery> locationQueryOpt = this.handleLocationFilter(queryFilter); if (locationQueryOpt.isPresent()) { FiltersHQLQuery locationHQLQuery = locationQueryOpt.get(); @@ -365,12 +364,12 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF } } - case NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> { // We authorize only a single constraint here // FIXME: Actually maybe we should allow specifying multiple equals constraints? LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - if (NotificationFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( + if (NotificationCustomFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( constraint.getValue())) { queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); @@ -465,10 +464,10 @@ private String handleSortEntries(List<LiveDataQuery.SortEntry> sortEntries) sortOperator = "desc"; } switch (sortEntry.getProperty()) { - case NotificationFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> + case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> clauses.add(String.format("nfp.enabled %s", sortOperator)); - case NotificationFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { if (sortEntry.isDescending()) { clauses.add("nfp.alertEnabled desc, nfp.emailEnabled asc"); } else { @@ -476,8 +475,8 @@ private String handleSortEntries(List<LiveDataQuery.SortEntry> sortEntries) } } - case NotificationFiltersLiveDataConfigurationProvider.LOCATION_FIELD, - NotificationFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { + case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, + NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { if (sortEntry.isDescending()) { clauses.add("nfp.user desc, nfp.wiki desc, nfp.page desc, nfp.pageOnly desc"); } else { @@ -485,10 +484,10 @@ private String handleSortEntries(List<LiveDataQuery.SortEntry> sortEntries) } } - case NotificationFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> + case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> clauses.add(String.format("nfp.filterType %s", sortOperator)); - case NotificationFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> + case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataPropertyDescriptorStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java similarity index 81% rename from xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataPropertyDescriptorStore.java rename to xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java index 80e9a16b72a2..5c24883c2ea7 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataPropertyDescriptorStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java @@ -17,7 +17,7 @@ * 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; +package org.xwiki.notifications.filters.internal.livedata.custom; import java.util.Collection; @@ -33,17 +33,17 @@ import org.xwiki.livedata.LiveDataPropertyDescriptorStore; /** - * Descriptor for the {@link NotificationFiltersLiveDataSource}. + * Descriptor for the {@link NotificationCustomFiltersLiveDataSource}. * * @version $Id$ */ @Component @Singleton -@Named(NotificationFiltersLiveDataSource.NAME) -public class NotificationFiltersLiveDataPropertyDescriptorStore implements LiveDataPropertyDescriptorStore +@Named(NotificationCustomFiltersLiveDataSource.NAME) +public class NotificationCustomFiltersLiveDataPropertyDescriptorStore implements LiveDataPropertyDescriptorStore { @Inject - @Named(NotificationFiltersLiveDataSource.NAME) + @Named(NotificationCustomFiltersLiveDataSource.NAME) private Provider<LiveDataConfiguration> liveDataConfigurationProvider; @Override diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java similarity index 86% rename from xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java rename to xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java index 968bbc521d99..e059acf32c22 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFiltersLiveDataSource.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java @@ -17,8 +17,7 @@ * 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; +package org.xwiki.notifications.filters.internal.livedata.custom; import javax.inject.Inject; import javax.inject.Named; @@ -31,10 +30,10 @@ @Component @Singleton -@Named(NotificationFiltersLiveDataSource.NAME) -public class NotificationFiltersLiveDataSource implements LiveDataSource +@Named(NotificationCustomFiltersLiveDataSource.NAME) +public class NotificationCustomFiltersLiveDataSource implements LiveDataSource { - static final String NAME = "notificationFilters"; + static final String NAME = "notificationCustomFilters"; @Inject @Named(NAME) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java new file mode 100644 index 000000000000..0466db12136b --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java @@ -0,0 +1,169 @@ +/* + * 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.system; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataConfiguration; +import org.xwiki.livedata.LiveDataEntryDescriptor; +import org.xwiki.livedata.LiveDataMeta; +import org.xwiki.livedata.LiveDataPaginationConfiguration; +import org.xwiki.livedata.LiveDataPropertyDescriptor; +import org.xwiki.localization.ContextualLocalizationManager; +import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; + +/** + * Configuration of the {@link NotificationSystemFiltersLiveDataSource}. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationSystemFiltersLiveDataSource.NAME) +public class NotificationSystemFiltersLiveDataConfigurationProvider implements Provider<LiveDataConfiguration> +{ + static final String NAME_FIELD = "name"; + static final String DESCRIPTION_FIELD = "description"; + static final String NOTIFICATION_FORMATS_FIELD = "notificationFormats"; + static final String IS_ENABLED_FIELD = "isEnabled"; + private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.system.table."; + private static final String STRING_TYPE = "String"; + private static final String HTML_DISPLAYER = "html"; + + @Inject + private ContextualLocalizationManager l10n; + + @Inject + private NotificationFilterLiveDataTranslationHelper translationHelper; + + @Override + public LiveDataConfiguration get() + { + LiveDataConfiguration input = new LiveDataConfiguration(); + LiveDataMeta meta = new LiveDataMeta(); + input.setMeta(meta); + + LiveDataPaginationConfiguration pagination = new LiveDataPaginationConfiguration(); + pagination.setShowPageSizeDropdown(false); + meta.setPagination(pagination); + + LiveDataEntryDescriptor entryDescriptor = new LiveDataEntryDescriptor(); + entryDescriptor.setIdProperty(NAME_FIELD); + meta.setEntryDescriptor(entryDescriptor); + + meta.setPropertyDescriptors(List.of( + getNameDescriptor(), + getDescriptionDescriptor(), + getNotificationFormatsDescriptor(), + getIsEnabledDescriptor() + )); + + return input; + } + + private LiveDataPropertyDescriptor getNameDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "name")); + descriptor.setId(NAME_FIELD); + descriptor.setType(STRING_TYPE); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(false); + descriptor.setFilterable(false); + + return descriptor; + } + + private LiveDataPropertyDescriptor getDescriptionDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "description")); + descriptor.setId(DESCRIPTION_FIELD); + descriptor.setType(STRING_TYPE); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(false); + descriptor.setFilterable(false); + + return descriptor; + } + + private LiveDataPropertyDescriptor.FilterDescriptor createFilterList(List<Map<String, String>> options) + { + LiveDataPropertyDescriptor.FilterDescriptor filterList = + new LiveDataPropertyDescriptor.FilterDescriptor("list"); + filterList.addOperator("empty", null); + filterList.setParameter("options", options); + filterList.addOperator("equals", null); + filterList.setDefaultOperator("equals"); + return filterList; + } + + private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "notificationFormats")); + descriptor.setId(NOTIFICATION_FORMATS_FIELD); + descriptor.setType(STRING_TYPE); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("html")); + descriptor.setFilter(createFilterList(Stream.of(NotificationFormat.values()).map(item -> + Map.of( + "value", item.name(), + "label", this.translationHelper.getFormatTranslation(item) + )).collect(Collectors.toList()))); + descriptor.setVisible(false); + descriptor.setEditable(false); + descriptor.setSortable(false); + descriptor.setFilterable(true); + + return descriptor; + } + + private LiveDataPropertyDescriptor getIsEnabledDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "isEnabled")); + descriptor.setId(IS_ENABLED_FIELD); + descriptor.setType("Boolean"); + LiveDataPropertyDescriptor.FilterDescriptor filterBoolean = + new LiveDataPropertyDescriptor.FilterDescriptor("boolean"); + descriptor.setFilter(filterBoolean); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("toggle")); + descriptor.setVisible(true); + descriptor.setEditable(false); + descriptor.setSortable(false); + descriptor.setFilterable(false); + + return descriptor; + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java new file mode 100644 index 000000000000..c1450ac098f5 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java @@ -0,0 +1,53 @@ +/* + * 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.system; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataConfiguration; +import org.xwiki.livedata.LiveDataConfigurationResolver; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.internal.JSONMerge; + +/** + * Needed component for the live data configuration. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationSystemFiltersLiveDataSource.NAME) +public class NotificationSystemFiltersLiveDataConfigurationResolver implements + LiveDataConfigurationResolver<LiveDataConfiguration> +{ + @Inject + @Named(NotificationSystemFiltersLiveDataSource.NAME) + private Provider<LiveDataConfiguration> notificationFiltersLiveDataConfigurationProvider; + + @Override + public LiveDataConfiguration resolve(LiveDataConfiguration input) throws LiveDataException + { + return new JSONMerge().merge(input, this.notificationFiltersLiveDataConfigurationProvider.get()); + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java new file mode 100644 index 000000000000..401fc2d412b1 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -0,0 +1,204 @@ +/* + * 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.system; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveData; +import org.xwiki.livedata.LiveDataEntryStore; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.LiveDataQuery; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.model.reference.LocalDocumentReference; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.notifications.NotificationException; +import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.NotificationFilterManager; +import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; +import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; +import org.xwiki.user.UserReference; +import org.xwiki.user.UserReferenceResolver; +import org.xwiki.wiki.descriptor.WikiDescriptorManager; + +import com.xpn.xwiki.XWikiContext; + +/** + * Dedicated {@link LiveDataEntryStore} for the {@link NotificationSystemFiltersLiveDataSource}. + * This component is in charge of performing the actual HQL queries to display the live data. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationSystemFiltersLiveDataSource.NAME) +public class NotificationSystemFiltersLiveDataEntryStore implements LiveDataEntryStore +{ + private static final String STARTS_WITH_OPERATOR = "startsWith"; + private static final String CONTAINS_OPERATOR = "contains"; + private static final String EQUALS_OPERATOR = "equals"; + 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."; + private static final LocalDocumentReference NOTIFICATION_ADMINISTRATION_REF = + new LocalDocumentReference(List.of("XWiki", "Notifications", "Code"), "NotificationAdministration"); + + @Inject + private Provider<XWikiContext> contextProvider; + + @Inject + private NotificationFilterManager notificationFilterManager; + + @Inject + private DocumentReferenceResolver<String> documentReferenceResolver; + + @Inject + private UserReferenceResolver<String> userReferenceResolver; + + @Inject + private EntityReferenceSerializer<String> entityReferenceSerializer; + + @Inject + private NotificationFilterLiveDataTranslationHelper translationHelper; + + @Inject + private ContextualAuthorizationManager contextualAuthorizationManager; + + @Inject + private WikiDescriptorManager wikiDescriptorManager; + + @Override + public Optional<Map<String, Object>> get(Object entryId) throws LiveDataException + { + Optional<Map<String, Object>> result = Optional.empty(); + + return result; + } + + private Map<String, Object> getPreferencesInformation(ToggleableNotificationFilter notificationFilter, + Map<String, Boolean> filterActions) + { + return Map.of( + NotificationSystemFiltersLiveDataConfigurationProvider.NAME_FIELD, + this.translationHelper.getTranslationWithPrefix("notifications.filters.name.", + notificationFilter.getName()), + NotificationSystemFiltersLiveDataConfigurationProvider.DESCRIPTION_FIELD, + this.translationHelper.getTranslationWithPrefix("notifications.filters.description.", + notificationFilter.getName()), + NotificationSystemFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, + this.displayNotificationFormats(notificationFilter), + NotificationSystemFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, + this.displayIsEnabled(notificationFilter, filterActions) + ); + } + + private String displayIsEnabled(ToggleableNotificationFilter notificationFilter, + Map<String, Boolean> filterActions) + { + boolean isEnabled = notificationFilter.isEnabledByDefault() || filterActions.get(notificationFilter.getName()); + String checked = (isEnabled) ? "checked=\"checked\"" : ""; + String html = "<input type=\"checkbox\" class=\"toggleableFilterPreferenceCheckbox\" " + + "data-filtername=\"%s\" %s />"; + return String.format(html, notificationFilter.getName(), checked); + } + + private String displayNotificationFormats(ToggleableNotificationFilter filter) + { + StringBuilder result = new StringBuilder("<ul class=\"list-unstyled\">"); + + for (NotificationFormat notificationFormat : filter.getFormats()) { + result.append("<li>"); + result.append(this.translationHelper.getFormatTranslation(notificationFormat)); + result.append("</li>"); + } + result.append("</ul>"); + + return result.toString(); + } + + @Override + public LiveData get(LiveDataQuery query) throws LiveDataException + { + if (query.getOffset() > Integer.MAX_VALUE) { + throw new LiveDataException("Currently only integer offsets are supported."); + } + 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)); + List<ToggleableNotificationFilter> filters = null; + Map<String, Boolean> filtersActivations = null; + boolean isAuthorized = false; + try { + if (WIKI_SOURCE_PARAMETER.equals(target)) { + WikiReference wikiReference = + new WikiReference(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER))); + if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN, wikiReference)) { + isAuthorized = true; + filters = this.notificationFilterManager.getToggleableFilters(wikiReference); + filtersActivations = + this.notificationFilterManager.getToggeableFilterActivations( + new DocumentReference(wikiReference, NOTIFICATION_ADMINISTRATION_REF)); + } + } else { + XWikiContext context = this.contextProvider.get(); + String targetValue = String.valueOf(sourceParameters.get(target)); + DocumentReference userDoc = + this.documentReferenceResolver.resolve(targetValue); + UserReference userReference = this.userReferenceResolver.resolve(targetValue); + if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN) + || context.getUserReference().equals(userDoc)) { + isAuthorized = true; + filters = this.notificationFilterManager.getToggleableFilters(userReference); + filtersActivations = this.notificationFilterManager.getToggeableFilterActivations(userDoc); + } + } + } catch (NotificationException e) { + throw new LiveDataException("Error when getting list of filters", e); + } + + if (!isAuthorized) { + throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); + } + + LiveData liveData = new LiveData(); + int offset = query.getOffset().intValue(); + if (offset < filters.size()) { + List<Map<String, Object>> entries = liveData.getEntries(); + for (int i = offset; i < Math.min(filters.size(), offset + query.getLimit()); i++) { + entries.add(getPreferencesInformation(filters.get(i), filtersActivations)); + } + liveData.setCount(entries.size()); + } + return liveData; + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java new file mode 100644 index 000000000000..86530eb74ccf --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java @@ -0,0 +1,54 @@ +/* + * 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.system; + +import java.util.Collection; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataConfiguration; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.LiveDataPropertyDescriptor; +import org.xwiki.livedata.LiveDataPropertyDescriptorStore; + +/** + * Descriptor for the {@link NotificationSystemFiltersLiveDataSource}. + * + * @version $Id$ + */ +@Component +@Singleton +@Named(NotificationSystemFiltersLiveDataSource.NAME) +public class NotificationSystemFiltersLiveDataPropertyDescriptorStore implements LiveDataPropertyDescriptorStore +{ + @Inject + @Named(NotificationSystemFiltersLiveDataSource.NAME) + private Provider<LiveDataConfiguration> liveDataConfigurationProvider; + + @Override + public Collection<LiveDataPropertyDescriptor> get() throws LiveDataException + { + return liveDataConfigurationProvider.get().getMeta().getPropertyDescriptors(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java new file mode 100644 index 000000000000..3d453d63e3ce --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java @@ -0,0 +1,57 @@ +/* + * 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.system; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataEntryStore; +import org.xwiki.livedata.LiveDataPropertyDescriptorStore; +import org.xwiki.livedata.LiveDataSource; + +@Component +@Singleton +@Named(NotificationSystemFiltersLiveDataSource.NAME) +public class NotificationSystemFiltersLiveDataSource implements LiveDataSource +{ + static final String NAME = "notificationSystemFilters"; + + @Inject + @Named(NAME) + private LiveDataEntryStore notificationFiltersLiveDataEntryStore; + + @Inject + @Named(NAME) + private LiveDataPropertyDescriptorStore notificationFiltersLiveDataPropertyDescriptorStore; + + @Override + public LiveDataEntryStore getEntries() + { + return this.notificationFiltersLiveDataEntryStore; + } + + @Override + public LiveDataPropertyDescriptorStore getProperties() + { + return this.notificationFiltersLiveDataPropertyDescriptorStore; + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt index 3f90af5e7e8c..d236c7ff52db 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt @@ -1,10 +1,15 @@ org.xwiki.notifications.filters.internal.event.NotificationFilterPreferenceEventConverter +org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataConfigurationProvider +org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataConfigurationResolver +org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataEntryStore +org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataPropertyDescriptorStore +org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataSource +org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataConfigurationProvider +org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataConfigurationResolver +org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataEntryStore +org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataPropertyDescriptorStore +org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataSource org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper -org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataConfigurationProvider -org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataConfigurationResolver -org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataEntryStore -org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataPropertyDescriptorStore -org.xwiki.notifications.filters.internal.livedata.NotificationFiltersLiveDataSource org.xwiki.notifications.filters.internal.migrators.NotificationFilterPreferencesMigrator org.xwiki.notifications.filters.internal.migrators.ScopeNotificationFilterClassMigrator org.xwiki.notifications.filters.internal.CachedModelBridgeInvalidatorListener diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index 019c9a1ab143..a2af30cbd19a 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -801,7 +801,6 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', </div> </div> #set ($sourceParameters = { - 'type': 'custom', 'target': $wikimacro.parameters.target }) #if ($wikimacro.parameters.target == 'user') @@ -812,7 +811,7 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', $services.liveData.render({ 'id': 'notificationCustomFilterPreferencesLiveData', 'properties': 'display,scope,location,filterType,eventTypes,notificationFormats,isEnabled,actions', - 'source': 'notificationFilters', + 'source': 'notificationCustomFilters', 'sourceParameters': $escapetool.url($sourceParameters), 'limit': 10 }) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml index 6d02977aad50..53e648b30296 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml @@ -599,37 +599,20 @@ path=$services.webjars.url('org.xwiki.platform:xwiki-platform-notifications-webj </div> {{/html}} #set ($sourceParameters = { - 'translationPrefix': 'notifications.settings.filters.preferences.system.table.', - 'resultPage': 'XWiki.Notifications.Code.NotificationFilterPreferenceLivetableResults', - 'eventType': '', - 'format': '', - 'type': 'system' + 'target': $wikimacro.parameters.target }) #if ($wikimacro.parameters.target == 'user') -#set($discard = $sourceParameters.put('user', $services.model.serialize($targetUser, 'default'))) + #set($discard = $sourceParameters.put('user', $services.model.serialize($targetUser, 'default'))) +#else + #set ($discard = $sourceParameters.put('wiki', $services.model.serialize($services.wiki.getCurrentWikiReference(), 'default'))) #end - -#set ($liveDataConfig = { - 'meta': { - 'propertyDescriptors': [ - { 'id': 'notificationFormats', 'displayer': 'html', 'filterable': false, 'sortable': false }, - { 'id': 'isEnabled', 'displayer': 'toggle', 'filterable': false, 'sortable': false }, - { 'id': 'name', 'filterable': false, 'sortable': false}, - { 'id': 'filterType', 'filterable': false, 'sortable': false} - ], - 'entryDescriptor': { - 'idProperty': 'filterPreferenceId' - } - } +$services.liveData.render({ + 'id': 'notificationSystemFilterPreferencesLiveData', + 'properties': 'name,description,notificationFormats,isEnabled', + 'source': 'notificationSystemFilters', + 'sourceParameters': $escapetool.url($sourceParameters), + 'limit': 10 }) -#set ($sourceParameters = $services.rendering.escape($escapetool.url($sourceParameters), 'xwiki/2.1')) -{{liveData - id='notificationSystemFilterPreferencesLiveData' - properties="name,filterType,notificationFormats,isEnabled" - source='liveTable' - sourceParameters="$sourceParameters" - limit='10' -}}$jsontool.serialize($liveDataConfig){{/liveData}} #end {{/velocity}}</code> </property> diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml index 6025577f9001..0d14c1f76faf 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml @@ -1,19 +1,36 @@ -<?xml version='1.1' encoding='UTF-8'?> +<?xml version="1.1" encoding="UTF-8"?> + +<!-- + * 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. +--> + <xwikidoc version="1.5" reference="XWiki.Notifications.Code.Translations" locale=""> <web>XWiki.Notifications.Code</web> <name>Translations</name> <language/> <defaultLanguage>en</defaultLanguage> <translation>0</translation> - <creator>XWiki.superadmin</creator> - <creationDate>1709550629000</creationDate> + <creator>xwiki:XWiki.Admin</creator> <parent>XWiki.Notifications.Code.WebHome</parent> - <author>XWiki.Admin</author> - <originalMetadataAuthor>XWiki.Admin</originalMetadataAuthor> - <contentAuthor>XWiki.Admin</contentAuthor> - <date>1709659831000</date> - <contentUpdateDate>1709659831000</contentUpdateDate> - <version>2.1</version> + <author>xwiki:XWiki.Admin</author> + <contentAuthor>xwiki:XWiki.Admin</contentAuthor> + <version>1.1</version> <title/> <comment/> <minorEdit>false</minorEdit> @@ -295,4 +312,4 @@ notifications.settings.filters.preferences.system.table.description=This table l <scope>WIKI</scope> </property> </object> -</xwikidoc> \ No newline at end of file +</xwikidoc> From 51d95a32036b885d2250873224f1027dee046519 Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Thu, 7 Mar 2024 17:02:09 +0100 Subject: [PATCH 08/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- ...mFiltersLiveDataConfigurationProvider.java | 30 ++----------------- ...cationSystemFiltersLiveDataEntryStore.java | 7 +++-- ...ficationsSystemFiltersPreferencesMacro.xml | 16 +++++----- .../XWiki/Notifications/Code/Translations.xml | 1 + 4 files changed, 17 insertions(+), 37 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java index 0466db12136b..d68bfc2cc26f 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java @@ -20,9 +20,6 @@ package org.xwiki.notifications.filters.internal.livedata.system; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Named; @@ -36,8 +33,6 @@ import org.xwiki.livedata.LiveDataPaginationConfiguration; import org.xwiki.livedata.LiveDataPropertyDescriptor; import org.xwiki.localization.ContextualLocalizationManager; -import org.xwiki.notifications.NotificationFormat; -import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; /** * Configuration of the {@link NotificationSystemFiltersLiveDataSource}. @@ -50,7 +45,7 @@ public class NotificationSystemFiltersLiveDataConfigurationProvider implements Provider<LiveDataConfiguration> { static final String NAME_FIELD = "name"; - static final String DESCRIPTION_FIELD = "description"; + static final String DESCRIPTION_FIELD = "filterDescription"; static final String NOTIFICATION_FORMATS_FIELD = "notificationFormats"; static final String IS_ENABLED_FIELD = "isEnabled"; private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.system.table."; @@ -60,9 +55,6 @@ public class NotificationSystemFiltersLiveDataConfigurationProvider implements P @Inject private ContextualLocalizationManager l10n; - @Inject - private NotificationFilterLiveDataTranslationHelper translationHelper; - @Override public LiveDataConfiguration get() { @@ -118,17 +110,6 @@ private LiveDataPropertyDescriptor getDescriptionDescriptor() return descriptor; } - private LiveDataPropertyDescriptor.FilterDescriptor createFilterList(List<Map<String, String>> options) - { - LiveDataPropertyDescriptor.FilterDescriptor filterList = - new LiveDataPropertyDescriptor.FilterDescriptor("list"); - filterList.addOperator("empty", null); - filterList.setParameter("options", options); - filterList.addOperator("equals", null); - filterList.setDefaultOperator("equals"); - return filterList; - } - private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); @@ -136,15 +117,10 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() descriptor.setId(NOTIFICATION_FORMATS_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("html")); - descriptor.setFilter(createFilterList(Stream.of(NotificationFormat.values()).map(item -> - Map.of( - "value", item.name(), - "label", this.translationHelper.getFormatTranslation(item) - )).collect(Collectors.toList()))); - descriptor.setVisible(false); + descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(false); - descriptor.setFilterable(true); + descriptor.setFilterable(false); return descriptor; } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index 401fc2d412b1..edf8bda051e3 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -19,6 +19,8 @@ */ package org.xwiki.notifications.filters.internal.livedata.system; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -40,6 +42,7 @@ import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationException; import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; @@ -62,9 +65,6 @@ @Named(NotificationSystemFiltersLiveDataSource.NAME) public class NotificationSystemFiltersLiveDataEntryStore implements LiveDataEntryStore { - private static final String STARTS_WITH_OPERATOR = "startsWith"; - private static final String CONTAINS_OPERATOR = "contains"; - private static final String EQUALS_OPERATOR = "equals"; 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."; @@ -189,6 +189,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException if (!isAuthorized) { throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); } + filters.sort(Comparator.comparing(NotificationFilter::getName)); LiveData liveData = new LiveData(); int offset = query.getOffset().intValue(); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml index 53e648b30296..43f57ed15223 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml @@ -606,13 +606,15 @@ path=$services.webjars.url('org.xwiki.platform:xwiki-platform-notifications-webj #else #set ($discard = $sourceParameters.put('wiki', $services.model.serialize($services.wiki.getCurrentWikiReference(), 'default'))) #end -$services.liveData.render({ - 'id': 'notificationSystemFilterPreferencesLiveData', - 'properties': 'name,description,notificationFormats,isEnabled', - 'source': 'notificationSystemFilters', - 'sourceParameters': $escapetool.url($sourceParameters), - 'limit': 10 -}) + +{{liveData + id='notificationSystemFilterPreferencesLiveData' + properties='name,description,notificationFormats,isEnabled' + source='notificationSystemFilters' + sourceParameters="$sourceParameters" + limit='10' +}}{{/liveData}} + #end {{/velocity}}</code> </property> diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml index 0d14c1f76faf..3f583784980f 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml @@ -95,6 +95,7 @@ notifications.settings.filters.preferences.system.title=System Filters notifications.settings.filters.preferences.system.hint=System filters that you can switches to have a more fine-grained control of the notifications you want to receive. ## Header definition for notification filter preferences notifications.settings.filters.preferences.system.table.name=Name +notifications.settings.filters.preferences.system.table.filterDescription=Description notifications.settings.filters.preferences.system.table.filterType=Description notifications.settings.filters.preferences.system.table.notificationFormats=Formats notifications.settings.filters.preferences.system.table.isEnabled=Is enabled? From bd0e9955f8bb89d633a3b1b0f74c6392d01a028e Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Fri, 8 Mar 2024 09:49:32 +0100 Subject: [PATCH 09/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- .../filters/NotificationFilterManager.java | 12 --- .../CachedFilterPreferencesModelBridge.java | 8 +- .../DefaultNotificationFilterManager.java | 33 +------ .../FilterPreferencesModelBridge.java | 3 +- .../ToggleableNotificationFilter.java | 8 -- ...oggleableNotificationFilterActivation.java | 96 +++++++++++++++++++ .../DefaultNotificationFilterManagerTest.java | 3 +- .../DefaultFilterPreferencesModelBridge.java | 17 ++-- ...cationSystemFiltersLiveDataEntryStore.java | 79 +++++++++------ ...ficationsSystemFiltersPreferencesMacro.xml | 2 +- 10 files changed, 171 insertions(+), 90 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java index b0b225ba301f..be1333d7ba4d 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java @@ -111,18 +111,6 @@ Stream<NotificationFilter> getFiltersRelatedToNotificationPreference(Collection< */ Stream<NotificationFilter> getToggleableFilters(Collection<NotificationFilter> filters); - default List<ToggleableNotificationFilter> getToggleableFilters(UserReference userReference) - throws NotificationException - { - return List.of(); - } - - default List<ToggleableNotificationFilter> getToggleableFilters(WikiReference wikiReference) - throws NotificationException - { - return List.of(); - } - /** * For all toggleable notification filters, get if the filter is enabled regarding the user profile. * diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/CachedFilterPreferencesModelBridge.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/CachedFilterPreferencesModelBridge.java index 264e194572ec..2df6c28e8b13 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/CachedFilterPreferencesModelBridge.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/CachedFilterPreferencesModelBridge.java @@ -75,7 +75,7 @@ public class CachedFilterPreferencesModelBridge implements FilterPreferencesMode private Map<EntityReference, Set<NotificationFilterPreference>> preferenceFilterCache; - private Map<EntityReference, Map<String, Boolean>> toggleCache; + private Map<EntityReference, Map<String, ToggleableNotificationFilterActivation>> toggleCache; void invalidatePreferencefilter(EntityReference reference) { @@ -132,10 +132,10 @@ public Set<NotificationFilterPreference> getFilterPreferences(WikiReference wiki } @Override - public Map<String, Boolean> getToggleableFilterActivations(DocumentReference userReference) - throws NotificationException + public Map<String, ToggleableNotificationFilterActivation> getToggleableFilterActivations( + DocumentReference userReference) throws NotificationException { - Map<String, Boolean> values = this.toggleCache.get(userReference); + Map<String, ToggleableNotificationFilterActivation> values = this.toggleCache.get(userReference); if (values != null) { return values; } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java index fab361814da2..9f4dc902caa6 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; @@ -79,10 +80,6 @@ public class DefaultNotificationFilterManager implements NotificationFilterManag @Named("cached") private FilterPreferencesModelBridge filterPreferencesModelBridge; - @Inject - @Named("document") - private UserReferenceSerializer<DocumentReference> documentReferenceUserReferenceSerializer; - @Override public Collection<NotificationFilter> getAllFilters(boolean allWikis) throws NotificationException { @@ -214,34 +211,14 @@ public Stream<NotificationFilter> getToggleableFilters(Collection<NotificationFi return filters.stream().filter(filter -> filter instanceof ToggleableNotificationFilter); } - @Override - public List<ToggleableNotificationFilter> getToggleableFilters(UserReference userReference) - throws NotificationException - { - DocumentReference userDoc = this.documentReferenceUserReferenceSerializer.serialize(userReference); - List<ToggleableNotificationFilter> result = new ArrayList<>(); - getAllFilters(userDoc, false) - .stream().filter(filter -> filter instanceof ToggleableNotificationFilter) - .forEach(item -> result.add((ToggleableNotificationFilter) item)); - return result; - } - - @Override - public List<ToggleableNotificationFilter> getToggleableFilters(WikiReference wikiReference) - throws NotificationException - { - List<ToggleableNotificationFilter> result = new ArrayList<>(); - getAllFilters(wikiReference) - .stream().filter(filter -> filter instanceof ToggleableNotificationFilter) - .forEach(item -> result.add((ToggleableNotificationFilter) item)); - return result; - } - @Override public Map<String, Boolean> getToggeableFilterActivations(DocumentReference user) throws NotificationException { - return filterPreferencesModelBridge.getToggleableFilterActivations(user); + return filterPreferencesModelBridge.getToggleableFilterActivations(user) + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().isEnabled())); } @Override diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/FilterPreferencesModelBridge.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/FilterPreferencesModelBridge.java index 4a02cb0c5965..c275f68a39e4 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/FilterPreferencesModelBridge.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/FilterPreferencesModelBridge.java @@ -75,7 +75,8 @@ default Set<NotificationFilterPreference> getFilterPreferences(WikiReference wik * @throws NotificationException if an error happens * @since 10.1RC1 */ - Map<String, Boolean> getToggleableFilterActivations(DocumentReference user) throws NotificationException; + Map<String, ToggleableNotificationFilterActivation> getToggleableFilterActivations(DocumentReference user) + throws NotificationException; /** * Delete a filter preference. diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java index 7d791a60e0af..d55a6c5f74cb 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java @@ -49,12 +49,4 @@ default List<NotificationFormat> getFormats() { return Arrays.asList(NotificationFormat.values()); } - - /** - * @return the events handled by this filter - */ - default List<String> getEventTypes() - { - return Collections.emptyList(); - } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java new file mode 100644 index 000000000000..8736bf3aa89c --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java @@ -0,0 +1,96 @@ +/* + * 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; + +import java.io.Serializable; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.xwiki.model.reference.DocumentReference; + +public class ToggleableNotificationFilterActivation implements Serializable +{ + private final String name; + private final boolean isEnabled; + private final DocumentReference objectLocation; + private final int objectNumber; + + public ToggleableNotificationFilterActivation(String name, boolean isEnabled, DocumentReference objectLocation, + int objectNumber) + { + this.name = name; + this.isEnabled = isEnabled; + this.objectLocation = objectLocation; + this.objectNumber = objectNumber; + } + + public String getName() + { + return name; + } + + public boolean isEnabled() + { + return isEnabled; + } + + public DocumentReference getObjectLocation() + { + return objectLocation; + } + + public int getObjectNumber() + { + return objectNumber; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + ToggleableNotificationFilterActivation that = (ToggleableNotificationFilterActivation) o; + + return new EqualsBuilder() + .append(isEnabled, that.isEnabled) + .append(objectNumber, that.objectNumber) + .append(name, that.name) + .append(objectLocation, that.objectLocation) + .isEquals(); + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(15, 33) + .append(name) + .append(isEnabled) + .append(objectLocation) + .append(objectNumber) + .toHashCode(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java index 2da8f7258975..45c72004f321 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java @@ -220,7 +220,8 @@ void getFiltersWithSpecificFilteringPhase() throws Exception filterActivations.put("filter6", false); // We don't put filter7 so it should default as being considered activated - when(this.filterPreferencesModelBridge.getToggleableFilterActivations(testUser)).thenReturn(filterActivations); + //when(this.filterPreferencesModelBridge.getToggleableFilterActivations(testUser)).thenReturn + // (filterActivations); when(filter1.getFilteringPhases()) .thenReturn(NotificationFilter.SUPPORT_ONLY_PRE_FILTERING_PHASE); when(filter2.getFilteringPhases()) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java index 56459f333601..05a1986cee02 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java @@ -19,7 +19,6 @@ */ package org.xwiki.notifications.filters.internal; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -39,7 +38,6 @@ import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceSerializer; -import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationException; import org.xwiki.notifications.NotificationFormat; @@ -97,30 +95,33 @@ public Set<NotificationFilterPreference> getFilterPreferences(WikiReference wiki } @Override - public Map<String, Boolean> getToggleableFilterActivations(DocumentReference user) throws NotificationException + public Map<String, ToggleableNotificationFilterActivation> getToggleableFilterActivations(DocumentReference user) throws NotificationException { XWikiContext context = contextProvider.get(); WikiReference currentWiki = context.getWikiReference(); context.setWikiReference(user.getWikiReference()); XWiki xwiki = context.getWiki(); - Map<String, Boolean> filterStatus = new HashMap<>(); + Map<String, ToggleableNotificationFilterActivation> filterStatus = new HashMap<>(); try { XWikiDocument doc = xwiki.getDocument(user, context); for (NotificationFilter filter : componentManager.<NotificationFilter>getInstanceList( - NotificationFilter.class)) { - if (filter instanceof ToggleableNotificationFilter) { - ToggleableNotificationFilter toggleableFilter = (ToggleableNotificationFilter) filter; + NotificationFilter.class)) { + if (filter instanceof ToggleableNotificationFilter toggleableFilter) { boolean status = toggleableFilter.isEnabledByDefault(); BaseObject obj = doc.getXObject(ToggleableFilterPreferenceDocumentInitializer.XCLASS, ToggleableFilterPreferenceDocumentInitializer.FIELD_FILTER_NAME, filter.getName(), false); + int objNumber = -1; if (obj != null) { status = obj.getIntValue(ToggleableFilterPreferenceDocumentInitializer.FIELD_IS_ENABLED, status ? 1 : 0) != 0; + objNumber = obj.getNumber(); } - filterStatus.put(filter.getName(), status); + ToggleableNotificationFilterActivation filterActivation = + new ToggleableNotificationFilterActivation(filter.getName(), status, user, objNumber); + filterStatus.put(filter.getName(), filterActivation); } } } catch (Exception e) { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index edf8bda051e3..0b4f17d98782 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -19,11 +19,11 @@ */ package org.xwiki.notifications.filters.internal.livedata.system; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; @@ -37,20 +37,18 @@ import org.xwiki.livedata.LiveDataQuery; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; -import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationException; import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; +import org.xwiki.notifications.filters.internal.FilterPreferencesModelBridge; import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; +import org.xwiki.notifications.filters.internal.ToggleableNotificationFilterActivation; import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; -import org.xwiki.user.UserReference; -import org.xwiki.user.UserReferenceResolver; -import org.xwiki.wiki.descriptor.WikiDescriptorManager; import com.xpn.xwiki.XWikiContext; @@ -78,13 +76,10 @@ public class NotificationSystemFiltersLiveDataEntryStore implements LiveDataEntr private NotificationFilterManager notificationFilterManager; @Inject - private DocumentReferenceResolver<String> documentReferenceResolver; - - @Inject - private UserReferenceResolver<String> userReferenceResolver; + private FilterPreferencesModelBridge filterPreferencesModelBridge; @Inject - private EntityReferenceSerializer<String> entityReferenceSerializer; + private DocumentReferenceResolver<String> documentReferenceResolver; @Inject private NotificationFilterLiveDataTranslationHelper translationHelper; @@ -92,9 +87,6 @@ public class NotificationSystemFiltersLiveDataEntryStore implements LiveDataEntr @Inject private ContextualAuthorizationManager contextualAuthorizationManager; - @Inject - private WikiDescriptorManager wikiDescriptorManager; - @Override public Optional<Map<String, Object>> get(Object entryId) throws LiveDataException { @@ -104,7 +96,7 @@ public Optional<Map<String, Object>> get(Object entryId) throws LiveDataExceptio } private Map<String, Object> getPreferencesInformation(ToggleableNotificationFilter notificationFilter, - Map<String, Boolean> filterActions) + ToggleableNotificationFilterActivation filterActivation) { return Map.of( NotificationSystemFiltersLiveDataConfigurationProvider.NAME_FIELD, @@ -116,18 +108,42 @@ private Map<String, Object> getPreferencesInformation(ToggleableNotificationFilt NotificationSystemFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, this.displayNotificationFormats(notificationFilter), NotificationSystemFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, - this.displayIsEnabled(notificationFilter, filterActions) + this.displayIsEnabled(notificationFilter, filterActivation), + "isEnabled_data", + this.displayIsEnabledData(notificationFilter, filterActivation), + "isEnabled_checked", + this.isEnabled(notificationFilter, filterActivation) ); } + private String getObjectNumber(ToggleableNotificationFilterActivation filterActivation) + { + return (filterActivation != null && filterActivation.getObjectNumber() != -1) ? + String.valueOf(filterActivation.getObjectNumber()) : ""; + } + + private Map<String, String> displayIsEnabledData(NotificationFilter notificationFilter, + ToggleableNotificationFilterActivation filterActivation) + { + return Map.of( + "objectNumber", getObjectNumber(filterActivation), + "filterName", notificationFilter.getName() + ); + } + + private boolean isEnabled(ToggleableNotificationFilter notificationFilter, + ToggleableNotificationFilterActivation filterActivation) + { + return notificationFilter.isEnabledByDefault() || (filterActivation != null && filterActivation.isEnabled()); + } + private String displayIsEnabled(ToggleableNotificationFilter notificationFilter, - Map<String, Boolean> filterActions) + ToggleableNotificationFilterActivation filterActivation) { - boolean isEnabled = notificationFilter.isEnabledByDefault() || filterActions.get(notificationFilter.getName()); - String checked = (isEnabled) ? "checked=\"checked\"" : ""; + String checked = (this.isEnabled(notificationFilter, filterActivation)) ? "checked=\"checked\"" : ""; String html = "<input type=\"checkbox\" class=\"toggleableFilterPreferenceCheckbox\" " - + "data-filtername=\"%s\" %s />"; - return String.format(html, notificationFilter.getName(), checked); + + "data-filterName=\"%s\" data-objectNumber='%s' %s />"; + return String.format(html, notificationFilter.getName(), getObjectNumber(filterActivation), checked); } private String displayNotificationFormats(ToggleableNotificationFilter filter) @@ -156,7 +172,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException } String target = String.valueOf(sourceParameters.get(TARGET_SOURCE_PARAMETER)); List<ToggleableNotificationFilter> filters = null; - Map<String, Boolean> filtersActivations = null; + Map<String, ToggleableNotificationFilterActivation> filtersActivations = null; boolean isAuthorized = false; try { if (WIKI_SOURCE_PARAMETER.equals(target)) { @@ -164,9 +180,13 @@ public LiveData get(LiveDataQuery query) throws LiveDataException new WikiReference(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER))); if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN, wikiReference)) { isAuthorized = true; - filters = this.notificationFilterManager.getToggleableFilters(wikiReference); + filters = this.notificationFilterManager.getAllFilters(wikiReference) + .stream() + .filter(filter -> filter instanceof ToggleableNotificationFilter) + .map(item -> (ToggleableNotificationFilter) item) + .collect(Collectors.toList()); filtersActivations = - this.notificationFilterManager.getToggeableFilterActivations( + this.filterPreferencesModelBridge.getToggleableFilterActivations( new DocumentReference(wikiReference, NOTIFICATION_ADMINISTRATION_REF)); } } else { @@ -174,12 +194,15 @@ public LiveData get(LiveDataQuery query) throws LiveDataException String targetValue = String.valueOf(sourceParameters.get(target)); DocumentReference userDoc = this.documentReferenceResolver.resolve(targetValue); - UserReference userReference = this.userReferenceResolver.resolve(targetValue); if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN) || context.getUserReference().equals(userDoc)) { isAuthorized = true; - filters = this.notificationFilterManager.getToggleableFilters(userReference); - filtersActivations = this.notificationFilterManager.getToggeableFilterActivations(userDoc); + filters = this.notificationFilterManager.getAllFilters(userDoc, false) + .stream() + .filter(filter -> filter instanceof ToggleableNotificationFilter) + .map(item -> (ToggleableNotificationFilter) item) + .collect(Collectors.toList()); + filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations(userDoc); } } } catch (NotificationException e) { @@ -196,7 +219,9 @@ public LiveData get(LiveDataQuery query) throws LiveDataException if (offset < filters.size()) { List<Map<String, Object>> entries = liveData.getEntries(); for (int i = offset; i < Math.min(filters.size(), offset + query.getLimit()); i++) { - entries.add(getPreferencesInformation(filters.get(i), filtersActivations)); + ToggleableNotificationFilter notificationFilter = filters.get(i); + entries.add(getPreferencesInformation(notificationFilter, + filtersActivations.get(notificationFilter.getName()))); } liveData.setCount(entries.size()); } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml index 43f57ed15223..cdcdb4287c9d 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml @@ -185,7 +185,7 @@ require(['jquery', 'xwiki-l10n!notifications-system-filters-preferences-macro', $(document).on('xwiki:livedata:toggle', '#notificationSystemFilterPreferencesLiveData', (event) => { const detail = event.detail; const data = detail.data - save(detail.checked, data.filtername, data.objectNumber, docURL).then(({checked, objectNumber}) => { + save(detail.checked, data.filterName, data.objectNumber, docURL).then(({checked, objectNumber}) => { detail.callback({ ...detail, data: { From fefbadd11233389253b5ca7f1c44d7852b92bce6 Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Fri, 8 Mar 2024 12:42:09 +0100 Subject: [PATCH 10/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- .../filters/NotificationFilterManager.java | 2 - .../DefaultNotificationFilterManager.java | 3 - .../ToggleableNotificationFilter.java | 1 - ...oggleableNotificationFilterActivation.java | 39 +++ .../DefaultNotificationFilterManagerTest.java | 17 +- .../checkstyle/checkstyle-suppressions.xml | 1 + .../DefaultFilterPreferencesModelBridge.java | 3 +- .../NotificationFilterPreferenceStore.java | 14 +- ...leFilterPreferenceDocumentInitializer.java | 12 +- ...cationFilterLiveDataTranslationHelper.java | 39 +++ ...mFiltersLiveDataConfigurationProvider.java | 47 ++- ...cationCustomFiltersLiveDataEntryStore.java | 273 ++++++++++-------- ...tificationCustomFiltersLiveDataSource.java | 6 + ...mFiltersLiveDataConfigurationProvider.java | 10 +- ...cationSystemFiltersLiveDataEntryStore.java | 9 +- ...tificationSystemFiltersLiveDataSource.java | 6 + ...faultFilterPreferencesModelBridgeTest.java | 18 +- 17 files changed, 337 insertions(+), 163 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java index be1333d7ba4d..0299d9a859eb 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/NotificationFilterManager.java @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -32,7 +31,6 @@ import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; import org.xwiki.notifications.preferences.NotificationPreference; import org.xwiki.rendering.block.Block; -import org.xwiki.user.UserReference; /** * Provide an interface for interacting with user notification filters. diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java index 9f4dc902caa6..542f45e0e560 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManager.java @@ -19,7 +19,6 @@ */ package org.xwiki.notifications.filters.internal; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -48,8 +47,6 @@ import org.xwiki.notifications.filters.NotificationFilterPreference; import org.xwiki.notifications.preferences.NotificationPreference; import org.xwiki.rendering.block.Block; -import org.xwiki.user.UserReference; -import org.xwiki.user.UserReferenceSerializer; import org.xwiki.wiki.descriptor.WikiDescriptorManager; /** diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java index d55a6c5f74cb..beeb2bbd8060 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilter.java @@ -20,7 +20,6 @@ package org.xwiki.notifications.filters.internal; import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.xwiki.notifications.NotificationFormat; diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java index 8736bf3aa89c..0a784ea35c91 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java @@ -24,8 +24,15 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.xwiki.model.reference.DocumentReference; +/** + * Provide information about the activation of {@link ToggleableNotificationFilter}. + * + * @version $Id$ + * @since 16.2.0RC1 + */ public class ToggleableNotificationFilterActivation implements Serializable { private final String name; @@ -33,6 +40,15 @@ public class ToggleableNotificationFilterActivation implements Serializable private final DocumentReference objectLocation; private final int objectNumber; + /** + * Default constructor. + * + * @param name the name of the filter + * @param isEnabled {@code true} if the filter is enabled + * @param objectLocation the location of the object holding activation information + * @param objectNumber the number of the actual object holding activation information or {@code -1} if there's no + * object + */ public ToggleableNotificationFilterActivation(String name, boolean isEnabled, DocumentReference objectLocation, int objectNumber) { @@ -42,21 +58,33 @@ public ToggleableNotificationFilterActivation(String name, boolean isEnabled, Do this.objectNumber = objectNumber; } + /** + * @return the name of the filter + */ public String getName() { return name; } + /** + * @return {@code true} if the filter is enabled + */ public boolean isEnabled() { return isEnabled; } + /** + * @return the location of the object holding activation information + */ public DocumentReference getObjectLocation() { return objectLocation; } + /** + * @return the number of the actual object holding activation information or {@code -1} if there's no object + */ public int getObjectNumber() { return objectNumber; @@ -93,4 +121,15 @@ public int hashCode() .append(objectNumber) .toHashCode(); } + + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("name", name) + .append("isEnabled", isEnabled) + .append("objectLocation", objectLocation) + .append("objectNumber", objectNumber) + .toString(); + } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java index 45c72004f321..74f9bffa9eac 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/test/java/org/xwiki/notifications/filters/internal/DefaultNotificationFilterManagerTest.java @@ -211,17 +211,16 @@ void getFiltersWithSpecificFilteringPhase() throws Exception when(filter6.getName()).thenReturn("filter6"); when(filter7.getName()).thenReturn("filter7"); - Map<String, Boolean> filterActivations = new HashMap<>(); - filterActivations.put("filter1", true); - filterActivations.put("filter2", false); - filterActivations.put("filter3", true); - filterActivations.put("filter4", false); - filterActivations.put("filter5", true); - filterActivations.put("filter6", false); + Map<String, ToggleableNotificationFilterActivation> filterActivations = new HashMap<>(); + filterActivations.put("filter1", new ToggleableNotificationFilterActivation("filter1", true, null, -1)); + filterActivations.put("filter2", new ToggleableNotificationFilterActivation("filter2", false, null, -1)); + filterActivations.put("filter3", new ToggleableNotificationFilterActivation("filter3", true, null, -1)); + filterActivations.put("filter4", new ToggleableNotificationFilterActivation("filter4", false, null, -1)); + filterActivations.put("filter5", new ToggleableNotificationFilterActivation("filter5", true, null, -1)); + filterActivations.put("filter6", new ToggleableNotificationFilterActivation("filter6", false, null, -1)); // We don't put filter7 so it should default as being considered activated - //when(this.filterPreferencesModelBridge.getToggleableFilterActivations(testUser)).thenReturn - // (filterActivations); + when(this.filterPreferencesModelBridge.getToggleableFilterActivations(testUser)).thenReturn(filterActivations); when(filter1.getFilteringPhases()) .thenReturn(NotificationFilter.SUPPORT_ONLY_PRE_FILTERING_PHASE); when(filter2.getFilteringPhases()) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/checkstyle/checkstyle-suppressions.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/checkstyle/checkstyle-suppressions.xml index 62cbad91217c..c5886cb2a9ab 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/checkstyle/checkstyle-suppressions.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/checkstyle/checkstyle-suppressions.xml @@ -28,4 +28,5 @@ <suppress checks="FanOutComplexity" files="NotificationFilterPreferenceStore.java"/> <suppress checks="FanOutComplexity" files="R140401000XWIKI15460DataMigration.java"/> <suppress checks="FanOutComplexity" files="R160000000XWIKI17243DataMigration.java"/> + <suppress checks="FanOutComplexity" files="NotificationCustomFiltersLiveDataEntryStore.java"/> </suppressions> diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java index 05a1986cee02..b466e61c52c5 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridge.java @@ -95,7 +95,8 @@ public Set<NotificationFilterPreference> getFilterPreferences(WikiReference wiki } @Override - public Map<String, ToggleableNotificationFilterActivation> getToggleableFilterActivations(DocumentReference user) throws NotificationException + public Map<String, ToggleableNotificationFilterActivation> getToggleableFilterActivations(DocumentReference user) + throws NotificationException { XWikiContext context = contextProvider.get(); WikiReference currentWiki = context.getWikiReference(); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java index bb6b569b3583..f23dc2f51aa4 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java @@ -65,6 +65,7 @@ public class NotificationFilterPreferenceStore { private static final String FILTER_PREFIX = "NFP_"; + private static final String ID = "id"; @Inject private EntityReferenceSerializer<String> entityReferenceSerializer; @@ -78,6 +79,15 @@ public class NotificationFilterPreferenceStore @Inject private ObservationManager observation; + /** + * Retrieve the notification preference that corresponds to the given id and wiki. + * + * @param wikiReference the wiki for which to retrieve a notification preference + * @param filterPreferenceId a filter preference id + * @return the corresponding preference or {@link Optional#empty()} if none can be found + * @throws NotificationException if an error occurs + * @since 16.2.0RC1 + */ public Optional<NotificationFilterPreference> getFilterPreference(String filterPreferenceId, WikiReference wikiReference) throws NotificationException { @@ -89,7 +99,7 @@ public Optional<NotificationFilterPreference> getFilterPreference(String filterP "select nfp from DefaultNotificationFilterPreference nfp where nfp.id = :id", Query.HQL); query.setLimit(1); - query.bindValue("id", filterPreferenceId); + query.bindValue(ID, filterPreferenceId); List<DefaultNotificationFilterPreference> results = query.execute(); if (!results.isEmpty()) { @@ -385,7 +395,7 @@ private void deleteFilterPreferences(WikiReference wikiReference, Set<Long> inte try { hibernateStore.executeWrite(context, session -> session.createQuery("delete from DefaultNotificationFilterPreference where internalId in (:id)") - .setParameter("id", internalFilterPreferenceIds) + .setParameter(ID, internalFilterPreferenceIds) .executeUpdate()); } catch (XWikiException e) { throw new NotificationException( diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java index c5566e6d401b..ad35097efeb8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java @@ -19,7 +19,6 @@ */ package org.xwiki.notifications.filters.internal; -import java.util.Arrays; import java.util.List; import javax.inject.Named; @@ -44,14 +43,19 @@ public class ToggleableFilterPreferenceDocumentInitializer extends AbstractMandatoryClassInitializer { /** - * The path to the class parent document. + * Reference of the xclass. */ - private static final List<String> PARENT_PATH = Arrays.asList("XWiki", "Notifications", "Code"); public static final LocalDocumentReference XCLASS = - new LocalDocumentReference(PARENT_PATH, "ToggleableFilterPreferenceClass"); + new LocalDocumentReference(List.of("XWiki", "Notifications", "Code"), "ToggleableFilterPreferenceClass"); + /** + * Name of field holding the filter name. + */ public static final String FIELD_FILTER_NAME = "filterName"; + /** + * Name of the field holding the activation value. + */ public static final String FIELD_IS_ENABLED = "isEnabled"; diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index 8d6654d929da..b780ee506433 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -36,6 +36,12 @@ import org.xwiki.notifications.filters.NotificationFilterType; import org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataConfigurationProvider; +/** + * Helper for getting various translations for live data custom sources. + * + * @version $Id$ + * @since 16.2.ORC1 + */ @Component(roles = NotificationFilterLiveDataTranslationHelper.class) @Singleton public class NotificationFilterLiveDataTranslationHelper @@ -55,27 +61,49 @@ private String getTranslationWithFallback(String translationKey) return translationPlain; } + /** + * Get a plain text translation with a fallback to the full key if none can be found. + * @param prefix the translation prefix to use + * @param key the key to add to the prefix + * @return the plain text translation or the prefix and key if none can be found + */ public String getTranslationWithPrefix(String prefix, String key) { return getTranslationWithFallback(prefix + key); } + /** + * @param filterType the filter type for which to get a translation + * @return the plain text translation of the filter type + */ public String getFilterTypeTranslation(NotificationFilterType filterType) { return getTranslationWithPrefix("notifications.filters.type.custom.", filterType.name().toLowerCase()); } + /** + * @param scope the scope for which to get a translation + * @return the plain text translation of the scope + */ public String getScopeTranslation(NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { return getTranslationWithPrefix("notifications.filters.preferences.scopeNotificationFilter.", scope.name().toLowerCase()); } + /** + * @return the translation for all events. + */ public String getAllEventTypesTranslation() { return getTranslationWithFallback("notifications.filters.preferences.allEvents"); } + /** + * @param eventType the event type for which to get a description translation + * @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 { try { @@ -88,11 +116,22 @@ public String getEventTypeTranslation(String eventType) throws LiveDataException } } + /** + * @param format the notification format for which to get a translation + * @return the plain text translation of the format + */ public String getFormatTranslation(NotificationFormat format) { return getTranslationWithPrefix("notifications.format.", format.name().toLowerCase()); } + /** + * 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 { try { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java index 72ed59298ead..55248bd079a1 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java @@ -56,7 +56,7 @@ @Named(NotificationCustomFiltersLiveDataSource.NAME) public class NotificationCustomFiltersLiveDataConfigurationProvider implements Provider<LiveDataConfiguration> { - public static final String ALL_EVENTS_OPTION_VALUE = "__ALL_EVENTS__"; + static final String ALL_EVENTS_OPTION_VALUE = "__ALL_EVENTS__"; static final String ID_FIELD = "filterPreferenceId"; static final String SCOPE_FIELD = "scope"; static final String LOCATION_FIELD = "location"; @@ -74,11 +74,31 @@ public class NotificationCustomFiltersLiveDataConfigurationProvider implements P private static final String VALUE_KEY = "value"; private static final String LABEL_KEY = "label"; + /** + * Defines the different scopes a filter might have. + * + * @version $Id$ + */ public enum Scope { + /** + * Filter preference targeting a wiki. + */ WIKI("wiki"), + + /** + * Filter preference targeting a space. + */ SPACE("page"), + + /** + * Filter preference targeting a page. + */ PAGE("pageOnly"), + + /** + * Filter preference targeting a user. + */ USER("user"); private final String fieldName; @@ -87,6 +107,9 @@ public enum Scope this.fieldName = fieldName; } + /** + * @return the database field name used to hold value of the concerned scope. + */ String getFieldName() { return this.fieldName; @@ -166,18 +189,19 @@ private LiveDataPropertyDescriptor.FilterDescriptor createFilterList(List<Map<St new LiveDataPropertyDescriptor.FilterDescriptor("list"); filterList.addOperator("empty", null); filterList.setParameter("options", options); - filterList.addOperator("equals", null); - filterList.setDefaultOperator("equals"); + String equalsOperator = "equals"; + filterList.addOperator(equalsOperator, null); + filterList.setDefaultOperator(equalsOperator); return filterList; } private LiveDataPropertyDescriptor getScopeDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "scope")); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + SCOPE_FIELD)); descriptor.setId(SCOPE_FIELD); descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("scope")); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(SCOPE_FIELD)); descriptor.setFilter(createFilterList(Stream.of(Scope.values()) .map(item -> Map.of( VALUE_KEY, item.name(), @@ -194,7 +218,7 @@ private LiveDataPropertyDescriptor getScopeDescriptor() private LiveDataPropertyDescriptor getLocationDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "location")); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + LOCATION_FIELD)); descriptor.setId(LOCATION_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); @@ -209,7 +233,7 @@ private LiveDataPropertyDescriptor getLocationDescriptor() private LiveDataPropertyDescriptor getFilterTypeDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "filterType")); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + FILTER_TYPE_FIELD)); descriptor.setId(FILTER_TYPE_FIELD); descriptor.setType(STRING_TYPE); descriptor.setFilter(createFilterList(Stream.of(NotificationFilterType.values()) @@ -228,10 +252,10 @@ private LiveDataPropertyDescriptor getFilterTypeDescriptor() private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "notificationFormats")); + 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")); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); descriptor.setFilter(createFilterList(Stream.of(NotificationFormat.values()).map(item -> Map.of( VALUE_KEY, item.name(), @@ -248,7 +272,7 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() private LiveDataPropertyDescriptor getEventTypesDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "eventTypes")); + 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)); @@ -257,6 +281,7 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor() 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)); } catch (LiveDataException e) { this.logger.error("Cannot provide event filter options", e); @@ -273,7 +298,7 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor() private LiveDataPropertyDescriptor getIsEnabledDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "isEnabled")); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + IS_ENABLED_FIELD)); descriptor.setId(IS_ENABLED_FIELD); descriptor.setType("Boolean"); LiveDataPropertyDescriptor.FilterDescriptor filterBoolean = diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index dd6bc4c3c722..daa4aee5b56b 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -20,7 +20,6 @@ package org.xwiki.notifications.filters.internal.livedata.custom; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -81,13 +80,15 @@ @Named(NotificationCustomFiltersLiveDataSource.NAME) public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntryStore { + private static final String WIKI = "wiki"; private static final String STARTS_WITH_OPERATOR = "startsWith"; private static final String CONTAINS_OPERATOR = "contains"; private static final String EQUALS_OPERATOR = "equals"; private static final String LOCATION_TEMPLATE = "notification/filters/livedatalocation.vm"; private static final String TARGET_SOURCE_PARAMETER = "target"; - private static final String WIKI_SOURCE_PARAMETER = "wiki"; + private static final String WIKI_SOURCE_PARAMETER = WIKI; private static final String UNAUTHORIZED_EXCEPTION_MSG = "You don't have rights to access those information."; + private static final String AND = " and "; @Inject private NotificationFilterPreferenceStore notificationFilterPreferenceStore; @@ -162,7 +163,7 @@ private Map<String, Object> getPreferenceInformation(NotificationFilterPreferenc { NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); - HashMap<String, Object> result = new LinkedHashMap<>(); + Map<String, Object> result = new LinkedHashMap<>(); // Map.of only accept 10 args result.put(NotificationCustomFiltersLiveDataConfigurationProvider.ID_FIELD, filterPreference.getId()); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, @@ -199,14 +200,24 @@ private String displayIsEnabled(NotificationFilterPreference filterPreference) private String displayLocation(NotificationFilterPreference filterPreference, NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { - EntityReference location = null; + EntityReference location; switch (scope) { - case WIKI -> location = this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); - case SPACE -> location = this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); - case PAGE -> location = this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), - EntityType.DOCUMENT); - case USER -> location = this.entityReferenceResolver.resolve(filterPreference.getUser(), - EntityType.DOCUMENT); + case USER: + location = this.entityReferenceResolver.resolve(filterPreference.getUser(), EntityType.DOCUMENT); + break; + + case WIKI: + location = this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); + break; + + case SPACE: + location = this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); + break; + + default: + case PAGE: + location = this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), EntityType.DOCUMENT); + break; } // FIXME: Do we need a new execution context? ScriptContext currentScriptContext = this.scriptContextManager.getCurrentScriptContext(); @@ -214,47 +225,64 @@ private String displayLocation(NotificationFilterPreference filterPreference, return this.templateManager.renderNoException(LOCATION_TEMPLATE); } - private String displayEventTypes(NotificationFilterPreference filterPreference) throws LiveDataException + private String getUnstyledList(List<String> items) { StringBuilder result = new StringBuilder("<ul class=\"list-unstyled\">"); + for (String item : items) { + result.append("<li>"); + result.append(item); + result.append("</li>"); + } + result.append("</ul>"); + return result.toString(); + } + + private String displayEventTypes(NotificationFilterPreference filterPreference) throws LiveDataException + { + String result; Set<String> eventTypes = filterPreference.getEventTypes(); if (eventTypes.isEmpty()) { - result.append("<li>"); - result.append(this.translationHelper.getAllEventTypesTranslation()); - result.append("<li>"); + result = getUnstyledList(List.of(this.translationHelper.getAllEventTypesTranslation())); } else { + List<String> items = new ArrayList<>(); for (String eventType : eventTypes) { - result.append("<li>"); - result.append(this.translationHelper.getEventTypeTranslation(eventType)); - result.append("</li>"); + items.add(this.translationHelper.getEventTypeTranslation(eventType)); } + result = getUnstyledList(items); } - result.append("</ul>"); - return result.toString(); + return result; } private String displayNotificationFormats(NotificationFilterPreference filterPreference) { - StringBuilder result = new StringBuilder("<ul class=\"list-unstyled\">"); - + List<String> items = new ArrayList<>(); for (NotificationFormat notificationFormat : filterPreference.getNotificationFormats()) { - result.append("<li>"); - result.append(this.translationHelper.getFormatTranslation(notificationFormat)); - result.append("</li>"); + items.add(this.translationHelper.getFormatTranslation(notificationFormat)); } - result.append("</ul>"); - return result.toString(); + return getUnstyledList(items); } private Map<String, String> getScopeInfo(NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { - String icon = ""; + String icon; switch (scope) { - case WIKI -> icon = "wiki"; - case SPACE -> icon = "chart-organisation"; - case PAGE -> icon = "page"; - case USER -> icon = "user"; + case USER: + icon = "user"; + break; + + case WIKI: + icon = WIKI; + break; + + case SPACE: + icon = "chart-organisation"; + break; + + default: + case PAGE: + icon = "page"; + break; } return Map.of("icon", icon, "name", this.translationHelper.getScopeTranslation(scope)); } @@ -296,10 +324,10 @@ private NotificationCustomFiltersLiveDataConfigurationProvider.Scope getScope(No } } - private static class FiltersHQLQuery + private static final class FiltersHQLQuery { - String whereClause; - Map<String, Object> bindings = new LinkedHashMap<>(); + private String whereClause; + private final Map<String, Object> bindings = new LinkedHashMap<>(); } private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryFilters) @@ -308,88 +336,35 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF List<String> queryWhereClauses = new ArrayList<>(); for (LiveDataQuery.Filter queryFilter : queryFilters) { switch (queryFilter.getProperty()) { - case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> { - // We only check first constraint: if there's more they are either redundant or contradictory. - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { - queryWhereClauses.add("nfp.enabled = 1"); - } else { - queryWhereClauses.add("nfp.enabled = 0"); - } - } + case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> + this.handleIsEnabledFilter(queryFilter, queryWhereClauses); - case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - String constraintValue = String.valueOf(constraint.getValue()); - if (EQUALS_OPERATOR.equals(constraint.getOperator()) - && !StringUtils.isEmpty(constraintValue)) { - if (NotificationFormat.ALERT.name().equals(constraintValue)) { - queryWhereClauses.add("nfp.alertEnabled = 1"); - } - if (NotificationFormat.EMAIL.name().equals(constraintValue)) { - queryWhereClauses.add("nfp.emailEnabled = 1"); - } - } - } + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> + this.handleNotificationFormatsFilter(queryFilter, queryWhereClauses); - case NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator()) - && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { - NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = - NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( - String.valueOf(constraint.getValue())); - queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); - } - } + case NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> + this.handleScopeFilter(queryFilter, queryWhereClauses); - case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - queryWhereClauses.add("nfp.filterType = :filterType"); - result.bindings.put("filterType", - NotificationFilterType.valueOf(String.valueOf(constraint.getValue()))); - } - } + case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> + this.handleFilterTypeFilter(queryFilter, queryWhereClauses, result); - case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> { - Optional<FiltersHQLQuery> locationQueryOpt = this.handleLocationFilter(queryFilter); - if (locationQueryOpt.isPresent()) { - FiltersHQLQuery locationHQLQuery = locationQueryOpt.get(); - queryWhereClauses.add(locationHQLQuery.whereClause); - result.bindings.putAll(locationHQLQuery.bindings); - } - } + case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> + this.handleLocationFilter(queryFilter, queryWhereClauses, result); - case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> { - // We authorize only a single constraint here - // FIXME: Actually maybe we should allow specifying multiple equals constraints? - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - if (NotificationCustomFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( - constraint.getValue())) - { - queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); - } else { - queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); - result.bindings.put("eventTypes", queryParameter); - } - } + case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> + this.handleEventTypeFilter(queryFilter, queryWhereClauses, result); + + default -> { } } } if (!queryWhereClauses.isEmpty()) { - StringBuilder stringBuilder = new StringBuilder(" and "); + StringBuilder stringBuilder = new StringBuilder(AND); Iterator<String> iterator = queryWhereClauses.iterator(); while (iterator.hasNext()) { stringBuilder.append(iterator.next()); if (iterator.hasNext()) { - stringBuilder.append(" and "); + stringBuilder.append(AND); } } result.whereClause = stringBuilder.toString(); @@ -399,9 +374,81 @@ private Optional<FiltersHQLQuery> handleFilter(List<LiveDataQuery.Filter> queryF } } - private Optional<FiltersHQLQuery> handleLocationFilter(LiveDataQuery.Filter locationFilter) + private void handleFilterTypeFilter(LiveDataQuery.Filter queryFilter, List<String> queryWhereClauses, + FiltersHQLQuery result) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + queryWhereClauses.add("nfp.filterType = :filterType"); + result.bindings.put("filterType", + NotificationFilterType.valueOf(String.valueOf(constraint.getValue()))); + } + } + + private void handleScopeFilter(LiveDataQuery.Filter queryFilter, List<String> queryWhereClauses) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { + NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = + NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( + String.valueOf(constraint.getValue())); + queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); + } + } + + private void handleNotificationFormatsFilter(LiveDataQuery.Filter queryFilter, List<String> queryWhereClauses) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + String constraintValue = String.valueOf(constraint.getValue()); + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isEmpty(constraintValue)) { + if (NotificationFormat.ALERT.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.alertEnabled = 1"); + } + if (NotificationFormat.EMAIL.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.emailEnabled = 1"); + } + } + } + + private void handleIsEnabledFilter(LiveDataQuery.Filter queryFilter, List<String> queryWhereClauses) + { + // We only check first constraint: if there's more they are either redundant or contradictory. + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { + queryWhereClauses.add("nfp.enabled = 1"); + } else { + queryWhereClauses.add("nfp.enabled = 0"); + } + } + + private void handleEventTypeFilter(LiveDataQuery.Filter queryFilter, List<String> queryWhereClauses, + FiltersHQLQuery result) + { + // We authorize only a single constraint here + // FIXME: Actually maybe we should allow specifying multiple equals constraints? + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + if (NotificationCustomFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( + constraint.getValue())) + { + queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); + } else { + queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put("eventTypes", queryParameter); + } + } + } + + private void handleLocationFilter(LiveDataQuery.Filter locationFilter, List<String> queryWhereClauses, + FiltersHQLQuery result) { - FiltersHQLQuery filtersHQLQuery = new FiltersHQLQuery(); List<String> clauses = new ArrayList<>(); int clauseCounter = 0; String clauseValue = "(nfp.pageOnly like :constraint_%1$s or nfp.page like :constraint_%1$s or " @@ -413,25 +460,22 @@ private Optional<FiltersHQLQuery> handleLocationFilter(LiveDataQuery.Filter loca + "nfp.wiki = :constraint_%1$s or nfp.user = :constraint_%1$s)", clauseCounter)); DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); queryParameter.literal(String.valueOf(constraint.getValue())); - filtersHQLQuery.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); } else if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { clauses.add(String.format(clauseValue, clauseCounter)); DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); queryParameter.literal(String.valueOf(constraint.getValue())).anyChars(); - filtersHQLQuery.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { clauses.add(String.format(clauseValue, clauseCounter)); DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); - filtersHQLQuery.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); } clauseCounter++; } if (!clauses.isEmpty()) { - filtersHQLQuery.whereClause = buildQueryClause(locationFilter, clauses); - return Optional.of(filtersHQLQuery); - } else { - return Optional.empty(); + queryWhereClauses.add(buildQueryClause(locationFilter, clauses)); } } @@ -440,7 +484,7 @@ private String buildQueryClause(LiveDataQuery.Filter filter, List<String> clause StringBuilder result = new StringBuilder("("); String operatorAppender; if (filter.isMatchAll()) { - operatorAppender = " and "; + operatorAppender = AND; } else { operatorAppender = " or "; } @@ -489,6 +533,9 @@ private String handleSortEntries(List<LiveDataQuery.SortEntry> sortEntries) case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); + + default -> { + } } } if (!clauses.isEmpty()) { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java index e059acf32c22..7c2fe6b2f904 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java @@ -28,6 +28,12 @@ import org.xwiki.livedata.LiveDataPropertyDescriptorStore; import org.xwiki.livedata.LiveDataSource; +/** + * Live data source for custom notification filters. + * + * @version $Id$ + * @since 16.2.0RC1 + */ @Component @Singleton @Named(NotificationCustomFiltersLiveDataSource.NAME) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java index d68bfc2cc26f..d70de002156d 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java @@ -83,7 +83,7 @@ public LiveDataConfiguration get() private LiveDataPropertyDescriptor getNameDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "name")); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NAME_FIELD)); descriptor.setId(NAME_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); @@ -98,7 +98,7 @@ private LiveDataPropertyDescriptor getNameDescriptor() private LiveDataPropertyDescriptor getDescriptionDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "description")); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + DESCRIPTION_FIELD)); descriptor.setId(DESCRIPTION_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); @@ -113,10 +113,10 @@ private LiveDataPropertyDescriptor getDescriptionDescriptor() private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "notificationFormats")); + 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")); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(false); @@ -128,7 +128,7 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() private LiveDataPropertyDescriptor getIsEnabledDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + "isEnabled")); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + IS_ENABLED_FIELD)); descriptor.setId(IS_ENABLED_FIELD); descriptor.setType("Boolean"); LiveDataPropertyDescriptor.FilterDescriptor filterBoolean = diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index 0b4f17d98782..5386bc95c6e0 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -90,9 +90,8 @@ public class NotificationSystemFiltersLiveDataEntryStore implements LiveDataEntr @Override public Optional<Map<String, Object>> get(Object entryId) throws LiveDataException { - Optional<Map<String, Object>> result = Optional.empty(); - - return result; + // We don't need to retrieve a single element for now. + return Optional.empty(); } private Map<String, Object> getPreferencesInformation(ToggleableNotificationFilter notificationFilter, @@ -118,8 +117,8 @@ private Map<String, Object> getPreferencesInformation(ToggleableNotificationFilt private String getObjectNumber(ToggleableNotificationFilterActivation filterActivation) { - return (filterActivation != null && filterActivation.getObjectNumber() != -1) ? - String.valueOf(filterActivation.getObjectNumber()) : ""; + return (filterActivation != null && filterActivation.getObjectNumber() != -1) + ? String.valueOf(filterActivation.getObjectNumber()) : ""; } private Map<String, String> displayIsEnabledData(NotificationFilter notificationFilter, diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java index 3d453d63e3ce..46a9d6f730f5 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java @@ -28,6 +28,12 @@ import org.xwiki.livedata.LiveDataPropertyDescriptorStore; import org.xwiki.livedata.LiveDataSource; +/** + * Live data source for notification system filter preferences. + * + * @version $Id$ + * @since 16.2.0RC1 + */ @Component @Singleton @Named(NotificationSystemFiltersLiveDataSource.NAME) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridgeTest.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridgeTest.java index cc96ec506a0d..b1b724afcca8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridgeTest.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/DefaultFilterPreferencesModelBridgeTest.java @@ -235,8 +235,10 @@ void getToggleableFilterActivations() throws Exception BaseObject filter4Obj = mock(BaseObject.class, "filter4Obj"); when(userDoc.getXObject(TOGGLEABLE_FILTER_PREFERENCE_CLASS, FIELD_FILTER_NAME, "filter2", false)).thenReturn(filter2Obj); + when(filter2Obj.getNumber()).thenReturn(2); when(userDoc.getXObject(TOGGLEABLE_FILTER_PREFERENCE_CLASS, FIELD_FILTER_NAME, "filter4", false)).thenReturn(filter4Obj); + when(filter4Obj.getNumber()).thenReturn(14); when(filter2Obj.getIntValue(FIELD_IS_ENABLED, 1)).thenReturn(1); when(filter4Obj.getIntValue(FIELD_IS_ENABLED, 1)).thenReturn(0); @@ -245,19 +247,21 @@ void getToggleableFilterActivations() throws Exception BaseObject filter7Obj = mock(BaseObject.class, "filter7Obj"); when(userDoc.getXObject(TOGGLEABLE_FILTER_PREFERENCE_CLASS, FIELD_FILTER_NAME, "filter6", false)).thenReturn(filter6Obj); + when(filter6Obj.getNumber()).thenReturn(16); when(userDoc.getXObject(TOGGLEABLE_FILTER_PREFERENCE_CLASS, FIELD_FILTER_NAME, "filter7", false)).thenReturn(filter7Obj); + when(filter7Obj.getNumber()).thenReturn(17); when(filter6Obj.getIntValue(FIELD_IS_ENABLED, 0)).thenReturn(1); when(filter7Obj.getIntValue(FIELD_IS_ENABLED, 0)).thenReturn(0); - Map<String, Boolean> expectedResult = Map.of( - "filter2", true, - "filter3", true, - "filter4", false, - "filter5", false, - "filter6", true, - "filter7", false + Map<String, ToggleableNotificationFilterActivation> expectedResult = Map.of( + "filter2", new ToggleableNotificationFilterActivation("filter2", true, user, 2), + "filter3", new ToggleableNotificationFilterActivation("filter3", true, user, -1), + "filter4", new ToggleableNotificationFilterActivation("filter4", false, user, 14), + "filter5", new ToggleableNotificationFilterActivation("filter5", false, user, -1), + "filter6", new ToggleableNotificationFilterActivation("filter6", true, user, 16), + "filter7", new ToggleableNotificationFilterActivation("filter7", false, user, 17) ); assertEquals(expectedResult, this.defaultModelBridge.getToggleableFilterActivations(user)); verify(context).setWikiReference(user.getWikiReference()); From 9de34026a80ec6a9cf4f85795f1310ff27ac17ea Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Fri, 8 Mar 2024 17:35:24 +0100 Subject: [PATCH 11/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source --- ...cationCustomFiltersLiveDataEntryStore.java | 10 +- ...cationSystemFiltersLiveDataEntryStore.java | 5 +- .../test/ui/NotificationsIT.java | 19 +- .../test/ui/NotificationsSettingsIT.java | 86 +++--- .../AbstractNotificationFilterPreference.java | 18 -- .../CustomNotificationFilterPreference.java | 10 +- .../filters/NotificationFilterPreference.java | 191 ------------- .../SystemNotificationFilterPreference.java | 13 +- ...cationFilterPreferenceLivetableResults.xml | 252 ------------------ ...ficationsSystemFiltersPreferencesMacro.xml | 24 +- 10 files changed, 90 insertions(+), 538 deletions(-) delete mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/NotificationFilterPreference.java delete mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationFilterPreferenceLivetableResults.xml diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index daa4aee5b56b..6c79d37f3fa8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -20,6 +20,7 @@ package org.xwiki.notifications.filters.internal.livedata.custom; import java.util.ArrayList; +import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -240,7 +241,9 @@ private String getUnstyledList(List<String> items) private String displayEventTypes(NotificationFilterPreference filterPreference) throws LiveDataException { String result; - Set<String> eventTypes = filterPreference.getEventTypes(); + List<String> eventTypes = new ArrayList<>(filterPreference.getEventTypes()); + // Ensure to always have same order + eventTypes.sort(Comparator.naturalOrder()); if (eventTypes.isEmpty()) { result = getUnstyledList(List.of(this.translationHelper.getAllEventTypesTranslation())); } else { @@ -256,7 +259,10 @@ private String displayEventTypes(NotificationFilterPreference filterPreference) private String displayNotificationFormats(NotificationFilterPreference filterPreference) { List<String> items = new ArrayList<>(); - for (NotificationFormat notificationFormat : filterPreference.getNotificationFormats()) { + List<NotificationFormat> notificationFormats = new ArrayList<>(filterPreference.getNotificationFormats()); + // Ensure to always have same order + notificationFormats.sort(Comparator.comparing(NotificationFormat::name)); + for (NotificationFormat notificationFormat : notificationFormats) { items.add(this.translationHelper.getFormatTranslation(notificationFormat)); } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index 5386bc95c6e0..d5de17701e19 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -133,7 +133,8 @@ private Map<String, String> displayIsEnabledData(NotificationFilter notification private boolean isEnabled(ToggleableNotificationFilter notificationFilter, ToggleableNotificationFilterActivation filterActivation) { - return notificationFilter.isEnabledByDefault() || (filterActivation != null && filterActivation.isEnabled()); + return (filterActivation != null && filterActivation.isEnabled()) || + (filterActivation == null && notificationFilter.isEnabledByDefault()); } private String displayIsEnabled(ToggleableNotificationFilter notificationFilter, @@ -186,7 +187,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException .collect(Collectors.toList()); filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations( - new DocumentReference(wikiReference, NOTIFICATION_ADMINISTRATION_REF)); + new DocumentReference(NOTIFICATION_ADMINISTRATION_REF, wikiReference)); } } else { XWikiContext context = this.contextProvider.get(); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsIT.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsIT.java index c193cbdd00cb..5fcf4cbca484 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsIT.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsIT.java @@ -243,8 +243,8 @@ void compositeNotifications(TestUtils setup, TestReference testReference, List<SystemNotificationFilterPreference> minorEvent = p.getSystemNotificationFilterPreferences() .stream() - .filter(fp -> fp.getFilterName().equals("Minor Event (Alert)")) - .collect(Collectors.toList()); + .filter(fp -> fp.getName().equals("Minor Event (Alert)")) + .toList(); assertEquals(1, minorEvent.size()); minorEvent.get(0).setEnabled(false); @@ -391,16 +391,18 @@ void ownEventNotifications(TestUtils setup, TestReference testReference) throws DocumentReference page2 = new DocumentReference("page2", testReference.getLastSpaceReference()); try { + int filterPreferenceNumber = 4; NotificationsUserProfilePage p = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); List<SystemNotificationFilterPreference> preferences = p.getSystemNotificationFilterPreferences(); // Now let's do some changes (own even filter) + SystemNotificationFilterPreference filterPreference = preferences.get(filterPreferenceNumber); p.setApplicationState(SYSTEM, "alert", BootstrapSwitch.State.ON); - assertEquals("Own Events Filter", preferences.get(2).getFilterName()); - preferences.get(2).setEnabled(false); + assertEquals("Own Events Filter", filterPreference.getName()); + filterPreference.setEnabled(false); setup.gotoPage(page2); p = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); - assertFalse(p.getSystemNotificationFilterPreferences().get(2).isEnabled()); + assertFalse(p.getSystemNotificationFilterPreferences().get(filterPreferenceNumber).isEnabled()); // Watch the entire wiki so that we receive notifications NotificationsTrayPage tray = new NotificationsTrayPage(); @@ -419,9 +421,10 @@ void ownEventNotifications(TestUtils setup, TestReference testReference) throws // Go back to enable the own even filter p = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); preferences = p.getSystemNotificationFilterPreferences(); - assertEquals("Own Events Filter", preferences.get(2).getFilterName()); - assertFalse(preferences.get(2).isEnabled()); - preferences.get(2).setEnabled(true); + filterPreference = preferences.get(filterPreferenceNumber); + assertEquals("Own Events Filter", filterPreference.getName()); + assertFalse(filterPreference.isEnabled()); + filterPreference.setEnabled(true); setup.createPage(page2, "", "Page 2"); setup.gotoPage(testReference.getLastSpaceReference().getName(), testReference.getName()); notificationsTrayPage = new NotificationsTrayPage(); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java index 70c6a67bf77f..ed9c94fcd5d3 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java @@ -198,42 +198,48 @@ void notificationFiltersDefaultValues(TestUtils testUtils) assertEquals(6, preferences.size()); // Filter 0 - assertEquals("Minor Event (Alert)", preferences.get(0).getFilterName()); - assertEquals("Hide notifications concerning minor changes on pages", preferences.get(0).getDescription()); - assertEquals(List.of("Alert"), preferences.get(0).getFormats()); - assertTrue(preferences.get(0).isEnabled()); + SystemNotificationFilterPreference filter0 = preferences.get(0); + assertEquals("Read Event Filter (Alert)", filter0.getName()); + assertEquals("Hide notifications that you have marked as read", filter0.getDescription()); + assertEquals(List.of("Alert"), filter0.getFormats()); + assertFalse(filter0.isEnabled()); // Filter 1 - assertEquals("Minor Event (Email)", preferences.get(1).getFilterName()); - assertEquals("Hide notifications concerning minor changes on pages", preferences.get(1).getDescription()); - assertEquals(List.of("Email"), preferences.get(1).getFormats()); - assertTrue(preferences.get(1).isEnabled()); + SystemNotificationFilterPreference filter1 = preferences.get(1); + assertEquals("Read Event Filter (Email)", filter1.getName()); + assertEquals("Hide notifications that you have marked as read", filter1.getDescription()); + assertEquals(List.of("Email"), filter1.getFormats()); + assertFalse(filter1.isEnabled()); // Filter 2 - assertEquals("Own Events Filter", preferences.get(2).getFilterName()); - assertEquals("Hide notifications about your own activity unless the event specifically targets you", - preferences.get(2).getDescription()); - assertEquals(List.of("Alert", "Email"), preferences.get(2).getFormats()); - assertTrue(preferences.get(2).isEnabled()); + SystemNotificationFilterPreference filter2 = preferences.get(2); + assertEquals("Minor Event (Alert)", filter2.getName()); + assertEquals("Hide notifications concerning minor changes on pages", filter2.getDescription()); + assertEquals(List.of("Alert"), filter2.getFormats()); + assertTrue(filter2.isEnabled()); // Filter 3 - assertEquals("Read Event Filter (Alert)", preferences.get(3).getFilterName()); - assertEquals("Hide notifications that you have marked as read", preferences.get(3).getDescription()); - assertEquals(List.of("Alert"), preferences.get(3).getFormats()); - assertFalse(preferences.get(3).isEnabled()); + SystemNotificationFilterPreference filter3 = preferences.get(3); + assertEquals("Minor Event (Email)", filter3.getName()); + assertEquals("Hide notifications concerning minor changes on pages", filter3.getDescription()); + assertEquals(List.of("Email"), filter3.getFormats()); + assertTrue(filter3.isEnabled()); // Filter 4 - assertEquals("Read Event Filter (Email)", preferences.get(4).getFilterName()); - assertEquals("Hide notifications that you have marked as read", preferences.get(4).getDescription()); - assertEquals(List.of("Email"), preferences.get(4).getFormats()); - assertFalse(preferences.get(4).isEnabled()); + SystemNotificationFilterPreference filter4 = preferences.get(4); + assertEquals("Own Events Filter", filter4.getName()); + assertEquals("Hide notifications about your own activity unless the event specifically targets you", + filter4.getDescription()); + assertEquals(List.of("Alert", "Email"), filter4.getFormats()); + assertTrue(filter4.isEnabled()); // Filter 5 - assertEquals("System Filter", preferences.get(5).getFilterName()); + SystemNotificationFilterPreference filter5 = preferences.get(5); + assertEquals("System Filter", filter5.getName()); assertEquals("Hide notifications from the System user unless the event specifically targets you", - preferences.get(5).getDescription()); - assertEquals(List.of("Alert", "Email"), preferences.get(5).getFormats()); - assertTrue(preferences.get(5).isEnabled()); + filter5.getDescription()); + assertEquals(List.of("Alert", "Email"), filter5.getFormats()); + assertTrue(filter5.isEnabled()); } @Test @@ -263,7 +269,7 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro assertEquals(1, preferences.size()); // Filter 0 - assertTrue(preferences.get(0).getFilterName().contains("Page only")); + assertEquals("Page only", preferences.get(0).getScope()); assertEquals(testReference.getLastSpaceReference().getName() + ".WebHome", preferences.get(0).getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, @@ -303,7 +309,7 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro assertEquals(1, preferences.size()); // Filter 1 - assertTrue(preferences.get(0).getFilterName().contains("Page and children")); + assertEquals("Page and children", preferences.get(0).getScope()); assertEquals(testReference.getLastSpaceReference().getName() + ".WebHome", preferences.get(0).getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, @@ -330,7 +336,7 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro assertEquals(2, preferences.size()); // Filter 2 - assertTrue(preferences.get(1).getFilterName().contains("Page only")); + assertEquals("Page only", preferences.get(1).getScope()); assertEquals(testReference.getLastSpaceReference().getName() + "." + testReference.getName(), preferences.get(1).getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.IGNORE_EVENT, @@ -604,7 +610,7 @@ void addCustomFilters(TestUtils testUtils) CustomNotificationFilterPreference filterPreference = customNotificationFilterPreferences.get(0); - assertTrue(filterPreference.getFilterName().contains("Page and children")); + assertEquals("Page and children", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".WebHome", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); assertEquals(List.of("Email"), filterPreference.getFormats()); @@ -625,7 +631,7 @@ void addCustomFilters(TestUtils testUtils) assertEquals(1, customNotificationFilterPreferences.size()); filterPreference = customNotificationFilterPreferences.get(0); - assertTrue(filterPreference.getFilterName().contains("Page and children")); + assertEquals("Page and children", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".WebHome", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); assertEquals(List.of("Email"), filterPreference.getFormats()); @@ -648,8 +654,7 @@ void addCustomFilters(TestUtils testUtils) filterPreference = customNotificationFilterPreferences.get(1); - assertTrue(filterPreference.getFilterName().contains("Page")); - assertFalse(filterPreference.getFilterName().contains("Page and children")); + assertEquals("Page only", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".SubSpace.SubPage", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.IGNORE_EVENT, filterPreference.getFilterAction()); @@ -669,7 +674,7 @@ void addCustomFilters(TestUtils testUtils) filterPreference = customNotificationFilterPreferences.get(0); - assertTrue(filterPreference.getFilterName().contains("Page and children")); + assertEquals("Page and children", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".WebHome", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); assertEquals(List.of("Email"), filterPreference.getFormats()); @@ -677,8 +682,7 @@ void addCustomFilters(TestUtils testUtils) filterPreference = customNotificationFilterPreferences.get(1); - assertTrue(filterPreference.getFilterName().contains("Page")); - assertFalse(filterPreference.getFilterName().contains("Page and children")); + assertEquals("Page only", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".SubSpace.SubPage", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.IGNORE_EVENT, filterPreference.getFilterAction()); @@ -705,21 +709,21 @@ void addCustomFilters(TestUtils testUtils) filterPreference = customNotificationFilterPreferences.get(2); - assertTrue(filterPreference.getFilterName().contains("Page")); - assertFalse(filterPreference.getFilterName().contains("Page and children")); + assertEquals("Page only", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".Page1", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); assertEquals(List.of("Alert"), filterPreference.getFormats()); - assertEquals(List.of("A page is modified", "A page is deleted"), filterPreference.getEventTypes()); + // core.events.delete before core.events.update + assertEquals(List.of("A page is deleted", "A page is modified"), filterPreference.getEventTypes()); filterPreference = customNotificationFilterPreferences.get(3); - assertTrue(filterPreference.getFilterName().contains("Page")); - assertFalse(filterPreference.getFilterName().contains("Page and children")); + assertEquals("Page only", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".Page2", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); assertEquals(List.of("Alert"), filterPreference.getFormats()); - assertEquals(List.of("A page is modified", "A page is deleted"), filterPreference.getEventTypes()); + // core.events.delete before core.events.update + assertEquals(List.of("A page is deleted","A page is modified"), filterPreference.getEventTypes()); } @Test diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/AbstractNotificationFilterPreference.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/AbstractNotificationFilterPreference.java index b0f6552ad412..653a4b5d1de6 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/AbstractNotificationFilterPreference.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/AbstractNotificationFilterPreference.java @@ -46,8 +46,6 @@ public abstract class AbstractNotificationFilterPreference private WebElement row; - private String filterName; - private List<String> formats = new ArrayList<>(); private BootstrapSwitch enabledSwitch; @@ -64,7 +62,6 @@ public AbstractNotificationFilterPreference(AbstractNotificationsSettingsPage pa { this.parentPage = parentPage; this.row = row; - this.filterName = getNameElement(row).getText(); List<WebElement> formatElements = getFormatsElement(row) .findElements(By.tagName("li")); for (WebElement format : formatElements) { @@ -74,13 +71,6 @@ public AbstractNotificationFilterPreference(AbstractNotificationsSettingsPage pa this.enabledSwitch = new BootstrapSwitch(getBootstrapSwitchElement(row), webDriver); } - /** - * @param row the row to get the name from - * @return the {@link WebElement} containing the name of the row - * @since 16.1.0RC1 - */ - protected abstract WebElement getNameElement(WebElement row); - /** * @param row the row to get the formats from * @return the {@link WebElement} containing the formats of the row @@ -100,14 +90,6 @@ private WebElement getBootstrapSwitchElement(WebElement row) return row.findElement(By.className("displayer-toggle")).findElement(By.className("bootstrap-switch")); } - /** - * @return the filter name. - */ - public String getFilterName() - { - return filterName; - } - /** * @return the parent page where the settings are displayed. */ diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/CustomNotificationFilterPreference.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/CustomNotificationFilterPreference.java index 910cce433547..53a14dfad0f0 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/CustomNotificationFilterPreference.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/CustomNotificationFilterPreference.java @@ -91,10 +91,12 @@ public CustomNotificationFilterPreference(AbstractNotificationsSettingsPage pare } } - @Override - protected WebElement getNameElement(WebElement row) + /** + * @return the scope of the filter. + */ + public String getScope() { - return row.findElement(By.cssSelector("td[data-title='Location']")); + return getRow().findElement(By.cssSelector("td[data-title='Scope']")).getText(); } /** @@ -110,7 +112,7 @@ public List<String> getEventTypes() */ public String getLocation() { - return this.getRow().findElement(By.cssSelector("td[data-title='Location'] .html-wrapper ol")) + return getRow().findElement(By.cssSelector("td[data-title='Location'] .html-wrapper ol")) .getAttribute("data-entity"); } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/NotificationFilterPreference.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/NotificationFilterPreference.java deleted file mode 100644 index df49f65ee77d..000000000000 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/NotificationFilterPreference.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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.platform.notifications.test.po.preferences.filters; - -import java.util.ArrayList; -import java.util.List; - -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; -import org.xwiki.platform.notifications.test.po.AbstractNotificationsSettingsPage; -import org.xwiki.test.ui.XWikiWebDriver; -import org.xwiki.test.ui.po.BootstrapSwitch; -import org.xwiki.test.ui.po.ConfirmationBox; - -/** - * Represent a livetable row describing a notification filter preference. - * - * @version $Id$ - * @since 10.8RC1 - * @since 9.11.8 - * @deprecated Since 13.2RC1: this page object is related to the usage of the deprecated XWiki macro - * {@code NotificationsFiltersPreferences} which was mixing system filters and custom filters. You should - * now use either {@link CustomNotificationFilterPreference} or {@link SystemNotificationFilterPreference}. - */ -@Deprecated -public class NotificationFilterPreference -{ - private static final String LIST_HTML_TAG = "li"; - private static final String EVENT_TYPES = "eventTypes"; - - private AbstractNotificationsSettingsPage parentPage; - - private WebElement livetableRow; - - private String filterName; - - private String filterType; - - private List<String> eventTypes = new ArrayList<>(); - - private List<String> formats = new ArrayList<>(); - - private BootstrapSwitch enabledSwitch; - - /** - * Construct a NotificationFilterPreference. - * @param parentPage the user profile's page holding the livetable. - * @param webElement the livetable row - * @param driver the current webdriver in used - */ - public NotificationFilterPreference(AbstractNotificationsSettingsPage parentPage, WebElement webElement, - XWikiWebDriver driver) - { - this.parentPage = parentPage; - this.livetableRow = webElement; - - this.filterName = webElement.findElement(By.className("name")).getText(); - this.filterType = webElement.findElement(By.className("filterType")).getText(); - - // Since the changes performed in 13.2RC1, not all filters LT have the event types. - if (driver.hasElement(webElement, By.className(EVENT_TYPES))) { - List<WebElement> eventTypeElements = webElement.findElement(By.className(EVENT_TYPES)).findElements( - By.tagName(LIST_HTML_TAG)); - for (WebElement eventType : eventTypeElements) { - String text = eventType.getText(); - if (!"-".equals(text)) { - this.eventTypes.add(text); - } - } - } - - List<WebElement> formatElements = webElement.findElement(By.className("notificationFormats")) - .findElements(By.tagName(LIST_HTML_TAG)); - for (WebElement format : formatElements) { - this.formats.add(format.getText()); - } - - enabledSwitch = new BootstrapSwitch( - webElement.findElement(By.className("isEnabled")).findElement(By.className("bootstrap-switch")), - driver - ); - } - - /** - * @return the name of the filter - */ - public String getFilterName() - { - return filterName; - } - - /** - * @return the type of the filter - */ - public String getFilterType() - { - return filterType; - } - - /** - * @return the event types concerned by the filters (empty means "all"). - */ - public List<String> getEventTypes() - { - return eventTypes; - } - - /** - * @return the formats concerned by the filter - */ - public List<String> getFormats() - { - return formats; - } - - /** - * @return either or not the preference is enabled - */ - public boolean isEnabled() - { - return enabledSwitch.getState() == BootstrapSwitch.State.ON; - } - - /** - * Enable or disable the current filter. - * @param enabled either or not the filter must be enabled - * @throws Exception if the expected state cannot be set - */ - public void setEnabled(boolean enabled) throws Exception - { - if (isEnabled() == enabled) { - return; - } - - this.enabledSwitch.setState(enabled ? BootstrapSwitch.State.ON : BootstrapSwitch.State.OFF); - this.parentPage.waitForNotificationSuccessMessage("Filter preference saved!"); - } - - /** - * @return the watched location if the current filter is a ScopeNotificationFilter. - */ - public String getLocation() - { - return this.livetableRow.findElement(By.cssSelector("td.name ol")).getAttribute("data-entity"); - } - - /** - * Delete the filter preference. - */ - public void delete() - { - this.livetableRow.findElement(By.cssSelector("td.actions a.actiondelete")).click(); - ConfirmationBox confirmationBox = new ConfirmationBox(); - confirmationBox.clickYes(); - this.parentPage.waitForNotificationSuccessMessage("Filter preference deleted!"); - } - - @Override - public String toString() - { - return String.format("NotificationFilterPreference{filterName='%s', filterType='%', evenTypes=%s, formats=%s," - + " enabled=%s", filterName, filterName, eventTypes, formats, isEnabled()); - } - - /** - * @return the ID of the filter. - * @since 12.4 - */ - public String getID() - { - return this.livetableRow.findElement(By.className("notificationFilterPreferenceCheckbox")) - .getAttribute("data-preferenceid"); - } -} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/SystemNotificationFilterPreference.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/SystemNotificationFilterPreference.java index 9c93039b3390..ddf529d39fff 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/SystemNotificationFilterPreference.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/filters/SystemNotificationFilterPreference.java @@ -32,8 +32,6 @@ */ public class SystemNotificationFilterPreference extends AbstractNotificationFilterPreference { - private final String description; - /** * Default constructor. * @param parentPage the page where the settings are displayed. @@ -44,13 +42,14 @@ public SystemNotificationFilterPreference(AbstractNotificationsSettingsPage pare XWikiWebDriver webDriver) { super(parentPage, row, webDriver); - this.description = row.findElement(By.cssSelector("td[data-title='Description'] .view")).getText(); } - @Override - protected WebElement getNameElement(WebElement row) + /** + * @return the name of the filter. + */ + public String getName() { - return row.findElement(By.cssSelector("td[data-title='Name'] .view")); + return getRow().findElement(By.cssSelector("td[data-title='Name'] .view")).getText(); } /** @@ -58,6 +57,6 @@ protected WebElement getNameElement(WebElement row) */ public String getDescription() { - return this.description; + return getRow().findElement(By.cssSelector("td[data-title='Description'] .view")).getText(); } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationFilterPreferenceLivetableResults.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationFilterPreferenceLivetableResults.xml deleted file mode 100644 index 6d8040ff6808..000000000000 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationFilterPreferenceLivetableResults.xml +++ /dev/null @@ -1,252 +0,0 @@ -<?xml version="1.1" encoding="UTF-8"?> - -<!-- - * 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. ---> - -<xwikidoc version="1.5" reference="XWiki.Notifications.Code.NotificationFilterPreferenceLivetableResults" locale=""> - <web>XWiki.Notifications.Code</web> - <name>NotificationFilterPreferenceLivetableResults</name> - <language/> - <defaultLanguage/> - <translation>0</translation> - <creator>xwiki:XWiki.Admin</creator> - <parent>XWiki.Notifications.Code.WebHome</parent> - <author>xwiki:XWiki.Admin</author> - <contentAuthor>xwiki:XWiki.Admin</contentAuthor> - <version>1.1</version> - <title>NotificationFilterPreferenceLivetableResults - - false - xwiki/2.1 - true - {{velocity wiki="false"}} -#if ($xcontext.action == 'get') -## -## Offset = item # at which to start displaying data -## -#set ($offset = $numbertool.toNumber($request.get('offset'))) -## offset starts from 0 in velocity and 1 in javascript -#set ($offset = $offset - 1) -#if ($offset < 0) - #set ($offset = 0) -#end -## -## Limit = # of items to display -## -#set ($limit = $numbertool.toNumber($request.get('limit'))) -#set ($limitOffset = $offset + $limit) -## -## Build the list of NotificationFilterPreference related to the given user. -## -#set ($isAccessGranted = false) -#if ("$!request.user" != "") - #set ($requestedUserDocRef = $services.model.resolveDocument($request.user)) - #set ($isAccessGranted = ($services.security.authorization.hasAccess('admin', $requestedUserDocRef) || $xcontext.userReference.equals($requestedUserDocRef))) - #set ($document = $xwiki.getDocument($requestedUserDocRef)) - #set ($target = $request.user) - #set ($userTarget = true) -## when request user is not given we are sending the list of NotificationFilterPreference of the current wiki. -#else - #set ($target = $services.wiki.getCurrentWikiReference()) - #set ($document = $xwiki.getDocument('XWiki.Notifications.Code.NotificationAdministration')) - #set ($userTarget = false) - #set ($isAccessGranted = true) -#end -#set ($elements = []) -#set ($index = 0) - -## For backward compatibility reason, we display both system filters and custom filters. -#set ($displayCustom = true) -#set ($displaySystem = true) -## This is the translation prefix used for filter types in the deprecated macro. -#set ($filterTypeTranslationPrefix = "notifications.filters.type.") - -#if ("$!request.type" == "custom") - #set ($displaySystem = false) - ## Those translations should now be used. - #set ($filterTypeTranslationPrefix = "notifications.filters.type.custom.") -#elseif ("$!request.type" == "system") - #set ($displayCustom = false) -#end - -#if ($displaySystem && $isAccessGranted) - ## First: get the list of available toggeable filters for the user - ## We display them first because we don't want them to be hidden by thousands of page filters the autowatch option - ## might have created. It would be not good to have to go to the last page of the livetable to find out these commonly - ## used filters. - #if ($userTarget) - #set ($filters = $services.notification.filters.getToggleableNotificationFilters($target)) - #else - #set ($filters = $services.notification.filters.getWikiToggleableNotificationFilters($target)) - #end - #foreach ($filter in $filters) - #set ($index = $index + 1) - ## Optimization to render only displayed rows (between $offset and $limitOffset) - #if ($index > $offset and $index <= $limitOffset) - #set ($checked = $filter.isEnabledByDefault()) - #set ($objectNumber = '') - #set ($obj = $document.getObject('XWiki.Notifications.Code.ToggleableFilterPreferenceClass', 'filterName', $filter.name)) - #if ($obj) - #set ($checked = $obj.getValue('isEnabled') != 0) - #set ($objectNumber = $!obj.reference.objectNumber) - #end - #if ($checked) - #set ($checkedAttr = 'checked = "checked"') - #else - #set ($checkedAttr = '') - #end - #set ($checkbox = "<input type='checkbox' class='toggleableFilterPreferenceCheckbox' data-objectNumber='${objectNumber}' data-filtername='${filter.name}' $checkedAttr />") - #set ($name = $services.localization.render("notifications.filters.name.$filter.name")) - #set ($element = { - 'filterPreferenceId' : $name, - 'name' : $name, - 'filterType' : $services.localization.render("notifications.filters.description.$filter.name"), - 'eventTypes' : "#displayNotificationsEventTypeList($filter.eventTypes)", - 'notificationFormats' : "#displayNotificationFormatsList($filter.formats)", - 'isEnabled' : $checkbox, - 'doc_viewable' : true, - 'doc_hasdelete' : false, - 'isEnabled_checked': $checked, - 'isEnabled_data': { - 'objectNumber': $objectNumber, - 'filtername': $filter.name - } - }) - #set ($discard = $elements.add($element)) - #end - #end - #set ($elements = $collectiontool.sort($elements, ['name'])) -#end - -#if ($displayCustom && $isAccessGranted) - ## Also get the list of available filters for the user - #set ($filters = $collectiontool.arrayList) - #if ($userTarget) - #set ($discard = $filters.addAll($services.notification.filters.getFilters($target))) - #else - #set ($discard = $filters.addAll($services.notification.filters.getWikiFilters($target))) - #end - #set ($filters = $collectiontool.sort($filters, ['name'])) - #set ($displayHiddenDocument = "$xwiki.getUserPreference('displayHiddenDocuments')" == '1') - #foreach ($filter in $filters) - #set ($filtersPreferences = $collectiontool.arrayList) - #if ($userTarget) - #set ($discard = $filtersPreferences.addAll($services.notification.filters.getFilterPreferences($filter, $target))) - #else - #set ($discard = $filtersPreferences.addAll($services.notification.filters.getWikiFilterPreferences($filter, $target))) - #end - #set ($filtersPreferences = $collectiontool.sort($filtersPreferences, ['id'])) - #foreach ($preference in $filtersPreferences) - #set ($page = $preference.pageOnly) - #set ($space = $preference.page) - #if (!$displayHiddenDocument && ($stringtool.isNotBlank($page) && $xwiki.getDocument($page).isHidden() - || $stringtool.isNotBlank($space) && $xwiki.getDocument($services.model.resolveSpace($space, 'default')).isHidden())) - ## Don't display a preference for an hidden page or space. - ## Why? If a user has the autowatch option enabled and create a new hidden document (for example, when she creates an application with AWM, hidden code pages are created as well), - ## we don't want to pollute the livetable with these hidden pages that the user don't know about. Moreover, the notifications will filter events concerning hidden pages, - ## so even if the filter exists, it has no effect to the user. - ## Here we handle only the case where pages.size() == 1 or spaces.size() == 1. - ## In the case there are several elements in the lists, they might have different "hidden" properties, and we don't handle it - ## Note: in the current implementation of watched entities, it's never the case. - ## TODO: handle these cases too in case custom filters need this. - #else - #set ($index = $index + 1) - ## Optimization to render only displayed rows (between $offset and $limitOffset) - #if ($index > $offset and $index <= $limitOffset) - #set ($display = $services.rendering.render($services.notification.filters.displayFilterPreference($filter, $preference), 'html/5.0')) - #set ($isEnabled = '') - #set ($isDisabled = '') - ## The watchlist bridge does not handle disabling a specific filter - #if ($preference.id.startsWith('watchlist_')) - #set ($isDisabled = 'disabled = "disabled"') - #end - #if ($preference.isEnabled()) - #set ($isEnabled = 'checked = "checked"') - #end - #set ($isEnabled = "<input type='checkbox' class='notificationFilterPreferenceCheckbox' data-preferenceId='${preference.id}' $isEnabled $isDisabled />") - - ## In case of backport of 15.10.x, isEnabled_disabled must be introduced to disabled legacy filters. - #set ($element = { - 'filterPreferenceId' : $preference.id, - 'name' : $display, - 'filterType' : $services.localization.render("$filterTypeTranslationPrefix$!preference.getFilterType().name().toLowerCase()"), - 'eventTypes' : "#displayNotificationsEventTypeList($preference.eventTypes)", - 'notificationFormats' : "#displayNotificationFormatsList($preference.notificationFormats)", - 'isEnabled' : $isEnabled, - 'doc_viewable' : true, - 'isEnabled_checked': $preference.isEnabled(), - 'isEnabled_data': { - 'preferenceId': $preference.id - } - }) - #set ($discard = $elements.add($element)) - #end - #end - #end - #end -#end -#if ($isAccessGranted) - ## - ## JSON. - ## - #set ($discard = $response.setContentType('application/json')) - #set ($offset = $numbertool.toNumber($request.offset)) - #if (!$offset) - #set ($offset = 1) - #end - $jsontool.serialize({ - 'totalrows' : $index, - 'reqNo' : $request.reqNo, - 'returnedrows': $elements.size(), - 'offset' : $offset, - 'rows' : $elements - }) -#else - #set ($discard = $response.sendError(401)) -#end -#end ## (context action) -## -## MACROS -## -#macro (displayNotificationsEventTypeList $list) - <ul class="list-unstyled"> - #if ($list.isEmpty()) - <li>$escapetool.xml($services.localization.render('notifications.filters.preferences.allEvents'))</li> - #else - #set ($types = {}) - #foreach ($descriptor in $services.eventstream.getRecordableEventDescriptors($xcontext.isMainWiki())) - #set ($discard = $types.put($descriptor.eventType, "$!services.localization.render($descriptor.description)")) - #end - #foreach ($eventType in $list) - <li>$escapetool.xml($types.get($eventType))</li> - #end - #end - </ul> -#end -#macro (displayNotificationFormatsList $formatList) - #set ($sortedFormatList = $collectiontool.sort($formatList)) - <ul class="list-unstyled"> - #foreach ($notificationFormat in $sortedFormatList) - <li>$services.localization.render("notifications.format.$!notificationFormat.name().toLowerCase()")</li> - #end - </ul> -#end -{{/velocity}} - diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml index cdcdb4287c9d..e2fd864295c5 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml @@ -596,25 +596,23 @@ path=$services.webjars.url('org.xwiki.platform:xwiki-platform-notifications-webj $escapetool.xml($services.localization.render('notifications.settings.filters.preferences.system.hint')) </p> </div> -</div> -{{/html}} #set ($sourceParameters = { 'target': $wikimacro.parameters.target }) #if ($wikimacro.parameters.target == 'user') - #set($discard = $sourceParameters.put('user', $services.model.serialize($targetUser, 'default'))) + #set ($discard = $sourceParameters.put('user', $services.model.serialize($targetUser, 'default'))) #else - #set ($discard = $sourceParameters.put('wiki', $services.model.serialize($services.wiki.getCurrentWikiReference(), 'default'))) + #set ($discard = $sourceParameters.put('wiki', $services.model.serialize($services.wiki.getCurrentWikiReference(), 'default'))) #end - -{{liveData - id='notificationSystemFilterPreferencesLiveData' - properties='name,description,notificationFormats,isEnabled' - source='notificationSystemFilters' - sourceParameters="$sourceParameters" - limit='10' -}}{{/liveData}} - +$services.liveData.render({ + 'id': 'notificationSystemFilterPreferencesLiveData', + 'properties': 'name,filterDescription,notificationFormats,isEnabled', + 'source': 'notificationSystemFilters', + 'sourceParameters': $escapetool.url($sourceParameters), + 'limit': 10 +}) +</div> +{{/html}} #end {{/velocity}} From 3967dcef884f70968c72523720793e11700d6ae5 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 13 Mar 2024 11:51:08 +0100 Subject: [PATCH 12/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to 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 --- ...tNotificationFilterLiveDataEntryStore.java | 149 ++++++ ...cationFilterLiveDataTranslationHelper.java | 23 +- ...mFiltersLiveDataConfigurationProvider.java | 29 +- ...cationCustomFiltersLiveDataEntryStore.java | 450 ++---------------- .../NotificationCustomFiltersQueryHelper.java | 326 +++++++++++++ ...mFiltersLiveDataConfigurationProvider.java | 42 +- ...cationSystemFiltersLiveDataEntryStore.java | 134 ++---- .../main/resources/META-INF/components.txt | 1 + ...ficationsCustomFiltersPreferencesMacro.xml | 1 + ...ficationsSystemFiltersPreferencesMacro.xml | 1 + .../src/components/DisplayerStaticList.vue | 64 +++ .../src/main/vue/src/main.js | 3 +- 12 files changed, 665 insertions(+), 558 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java new file mode 100644 index 000000000000..499729fec22c --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java @@ -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 entityReferenceResolver; + + @Inject + protected EntityReferenceSerializer entityReferenceSerializer; + + @Inject + private Provider contextProvider; + + @Inject + private ContextualAuthorizationManager contextualAuthorizationManager; + + protected static final class TargetInformation + { + public boolean isWikiTarget; + public EntityReference ownerReference; + + } + + protected Map getStaticListInfo(List items) + { + return Map.of( + "extraClass", "list-unstyled", + "items", items + ); + } + + protected Map displayNotificationFormats(Collection notificationFormats) + { + List 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 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); + } + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index b780ee506433..c6f53fa54dd1 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -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; @@ -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 @@ -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); @@ -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; } } @@ -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> getAllEventTypesOptions(boolean allFarm) throws LiveDataException + public List> getAllEventTypesOptions() throws LiveDataException { try { + boolean isMainWiki = this.wikiDescriptorManager.isMainWiki(this.wikiDescriptorManager.getCurrentWikiId()); List recordableEventDescriptors = - this.recordableEventDescriptorManager.getRecordableEventDescriptors(allFarm); + this.recordableEventDescriptorManager.getRecordableEventDescriptors(isMainWiki); return recordableEventDescriptors.stream().map(descriptor -> Map.of( "value", descriptor.getEventType(), "label", getTranslationWithFallback(descriptor.getDescription()) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java index 55248bd079a1..232c630f7dfe 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java @@ -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; @@ -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"; @@ -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); } @@ -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); @@ -187,9 +191,9 @@ private LiveDataPropertyDescriptor.FilterDescriptor createFilterList(List 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); @@ -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); @@ -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); @@ -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> 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); } @@ -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); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index 6c79d37f3fa8..9031d7bd8fad 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -19,18 +19,14 @@ */ package org.xwiki.notifications.filters.internal.livedata.custom; -import java.util.ArrayList; import java.util.Comparator; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Provider; import javax.inject.Singleton; import javax.script.ScriptContext; @@ -43,33 +39,22 @@ import org.xwiki.livedata.LiveDataQuery; import org.xwiki.model.EntityType; 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.NotificationException; -import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.NotificationFilterPreference; -import org.xwiki.notifications.filters.NotificationFilterType; import org.xwiki.notifications.filters.internal.DefaultNotificationFilterPreference; import org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore; -import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; -import org.xwiki.query.Query; +import org.xwiki.notifications.filters.internal.livedata.AbstractNotificationFilterLiveDataEntryStore; import org.xwiki.query.QueryException; -import org.xwiki.query.QueryManager; -import org.xwiki.query.internal.DefaultQueryParameter; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.renderer.BlockRenderer; import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; import org.xwiki.rendering.renderer.printer.WikiPrinter; import org.xwiki.script.ScriptContextManager; -import org.xwiki.security.authorization.ContextualAuthorizationManager; -import org.xwiki.security.authorization.Right; import org.xwiki.template.TemplateManager; -import com.xpn.xwiki.XWikiContext; - /** * Dedicated {@link LiveDataEntryStore} for the {@link NotificationCustomFiltersLiveDataSource}. * This component is in charge of performing the actual HQL queries to display the live data. @@ -79,33 +64,17 @@ @Component @Singleton @Named(NotificationCustomFiltersLiveDataSource.NAME) -public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntryStore +public class NotificationCustomFiltersLiveDataEntryStore extends AbstractNotificationFilterLiveDataEntryStore { private static final String WIKI = "wiki"; - private static final String STARTS_WITH_OPERATOR = "startsWith"; - private static final String CONTAINS_OPERATOR = "contains"; - private static final String EQUALS_OPERATOR = "equals"; private static final String LOCATION_TEMPLATE = "notification/filters/livedatalocation.vm"; - 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."; - private static final String AND = " and "; @Inject private NotificationFilterPreferenceStore notificationFilterPreferenceStore; - @Inject - private Provider contextProvider; - @Inject private NotificationFilterManager notificationFilterManager; - @Inject - private EntityReferenceResolver entityReferenceResolver; - - @Inject - private EntityReferenceSerializer entityReferenceSerializer; - @Inject @Named("context") private ComponentManager componentManager; @@ -114,12 +83,6 @@ public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntr @Named("html/5.0") private BlockRenderer blockRenderer; - @Inject - private QueryManager queryManager; - - @Inject - private NotificationFilterLiveDataTranslationHelper translationHelper; - @Inject private TemplateManager templateManager; @@ -127,28 +90,21 @@ public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntr private ScriptContextManager scriptContextManager; @Inject - private ContextualAuthorizationManager contextualAuthorizationManager; + private NotificationCustomFiltersQueryHelper queryHelper; @Override public Optional> get(Object entryId) throws LiveDataException { Optional> result = Optional.empty(); - XWikiContext context = contextProvider.get(); - WikiReference wikiReference = context.getWikiReference(); Optional filterPreferenceOpt; try { - filterPreferenceOpt = notificationFilterPreferenceStore - .getFilterPreference(String.valueOf(entryId), wikiReference); + filterPreferenceOpt = this.notificationFilterPreferenceStore + .getFilterPreference(String.valueOf(entryId), getCurrentWikiReference()); if (filterPreferenceOpt.isPresent()) { DefaultNotificationFilterPreference filterPreference = (DefaultNotificationFilterPreference) filterPreferenceOpt.get(); - if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN) - || filterPreference.getOwner().equals( - this.entityReferenceSerializer.serialize(context.getUserReference()))) { - result = Optional.of(getPreferenceInformation(filterPreference)); - } else { - throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); - } + checkAccessFilterPreference(filterPreference); + result = Optional.of(getPreferenceInformation(filterPreference)); } } catch (NotificationException e) { throw new LiveDataException( @@ -170,7 +126,7 @@ private Map getPreferenceInformation(NotificationFilterPreferenc result.put(NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, this.displayEventTypes(filterPreference)); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, - displayNotificationFormats(filterPreference)); + displayNotificationFormats(filterPreference.getNotificationFormats())); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope)); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, this.displayLocation(filterPreference, scope)); @@ -178,9 +134,8 @@ private Map getPreferenceInformation(NotificationFilterPreferenc this.renderDisplay(filterPreference)); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType())); - result.put(NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, - displayIsEnabled(filterPreference)); result.put("isEnabled_checked", filterPreference.isEnabled()); + result.put("isEnabled_disabled", filterPreference.getId().startsWith("watchlist_")); result.put("isEnabled_data", Map.of( "preferenceId", filterPreference.getId() )); @@ -189,107 +144,46 @@ private Map getPreferenceInformation(NotificationFilterPreferenc return result; } - private String displayIsEnabled(NotificationFilterPreference filterPreference) - { - String checked = (filterPreference.isEnabled()) ? "checked=\"checked\"" : ""; - String disabled = (filterPreference.getId().startsWith("watchlist_")) ? "disabled=\"disabled\"" : ""; - String html = ""; - return String.format(html, filterPreference.getId(), checked, disabled); - } - private String displayLocation(NotificationFilterPreference filterPreference, NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { - EntityReference location; - switch (scope) { - case USER: - location = this.entityReferenceResolver.resolve(filterPreference.getUser(), EntityType.DOCUMENT); - break; - - case WIKI: - location = this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); - break; - - case SPACE: - location = this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); - break; - - default: - case PAGE: - location = this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), EntityType.DOCUMENT); - break; - } - // FIXME: Do we need a new execution context? + EntityReference location = switch (scope) { + case USER -> this.entityReferenceResolver.resolve(filterPreference.getUser(), EntityType.DOCUMENT); + case WIKI -> this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); + case SPACE -> this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); + case PAGE -> + this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), EntityType.DOCUMENT); + }; + // TODO: Create an improvment ticket for having a displayer ScriptContext currentScriptContext = this.scriptContextManager.getCurrentScriptContext(); currentScriptContext.setAttribute("location", location, ScriptContext.ENGINE_SCOPE); return this.templateManager.renderNoException(LOCATION_TEMPLATE); } - private String getUnstyledList(List items) + private Map displayEventTypes(NotificationFilterPreference filterPreference) { - StringBuilder result = new StringBuilder("
    "); - for (String item : items) { - result.append("
  • "); - result.append(item); - result.append("
  • "); - } - result.append("
"); - return result.toString(); - } - - private String displayEventTypes(NotificationFilterPreference filterPreference) throws LiveDataException - { - String result; - List eventTypes = new ArrayList<>(filterPreference.getEventTypes()); - // Ensure to always have same order - eventTypes.sort(Comparator.naturalOrder()); - if (eventTypes.isEmpty()) { - result = getUnstyledList(List.of(this.translationHelper.getAllEventTypesTranslation())); + List items; + if (filterPreference.getEventTypes().isEmpty()) { + items = List.of(this.translationHelper.getAllEventTypesTranslation()); } else { - List items = new ArrayList<>(); - for (String eventType : eventTypes) { - items.add(this.translationHelper.getEventTypeTranslation(eventType)); - } - result = getUnstyledList(items); + items = filterPreference.getEventTypes() + .stream() + .sorted(Comparator.naturalOrder()) + .map(eventType -> this.translationHelper.getEventTypeTranslation(eventType)) + .toList(); } - return result; - } - private String displayNotificationFormats(NotificationFilterPreference filterPreference) - { - List items = new ArrayList<>(); - List notificationFormats = new ArrayList<>(filterPreference.getNotificationFormats()); - // Ensure to always have same order - notificationFormats.sort(Comparator.comparing(NotificationFormat::name)); - for (NotificationFormat notificationFormat : notificationFormats) { - items.add(this.translationHelper.getFormatTranslation(notificationFormat)); - } - - return getUnstyledList(items); + return getStaticListInfo(items); } private Map getScopeInfo(NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { - String icon; - switch (scope) { - case USER: - icon = "user"; - break; - - case WIKI: - icon = WIKI; - break; - - case SPACE: - icon = "chart-organisation"; - break; - - default: - case PAGE: - icon = "page"; - break; - } + String icon = switch (scope) { + case USER -> "user"; + case WIKI -> WIKI; + case SPACE -> "chart-organisation"; + case PAGE -> "page"; + }; return Map.of("icon", icon, "name", this.translationHelper.getScopeTranslation(scope)); } @@ -330,285 +224,25 @@ private NotificationCustomFiltersLiveDataConfigurationProvider.Scope getScope(No } } - private static final class FiltersHQLQuery - { - private String whereClause; - private final Map bindings = new LinkedHashMap<>(); - } - - private Optional handleFilter(List queryFilters) - { - FiltersHQLQuery result = new FiltersHQLQuery(); - List queryWhereClauses = new ArrayList<>(); - for (LiveDataQuery.Filter queryFilter : queryFilters) { - switch (queryFilter.getProperty()) { - case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> - this.handleIsEnabledFilter(queryFilter, queryWhereClauses); - - case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> - this.handleNotificationFormatsFilter(queryFilter, queryWhereClauses); - - case NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> - this.handleScopeFilter(queryFilter, queryWhereClauses); - - case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> - this.handleFilterTypeFilter(queryFilter, queryWhereClauses, result); - - case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> - this.handleLocationFilter(queryFilter, queryWhereClauses, result); - - case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> - this.handleEventTypeFilter(queryFilter, queryWhereClauses, result); - - default -> { - } - } - } - if (!queryWhereClauses.isEmpty()) { - StringBuilder stringBuilder = new StringBuilder(AND); - Iterator iterator = queryWhereClauses.iterator(); - while (iterator.hasNext()) { - stringBuilder.append(iterator.next()); - if (iterator.hasNext()) { - stringBuilder.append(AND); - } - } - result.whereClause = stringBuilder.toString(); - return Optional.of(result); - } else { - return Optional.empty(); - } - } - - private void handleFilterTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, - FiltersHQLQuery result) - { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - queryWhereClauses.add("nfp.filterType = :filterType"); - result.bindings.put("filterType", - NotificationFilterType.valueOf(String.valueOf(constraint.getValue()))); - } - } - - private void handleScopeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) - { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator()) - && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { - NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = - NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( - String.valueOf(constraint.getValue())); - queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); - } - } - - private void handleNotificationFormatsFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) - { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - String constraintValue = String.valueOf(constraint.getValue()); - if (EQUALS_OPERATOR.equals(constraint.getOperator()) - && !StringUtils.isEmpty(constraintValue)) { - if (NotificationFormat.ALERT.name().equals(constraintValue)) { - queryWhereClauses.add("nfp.alertEnabled = 1"); - } - if (NotificationFormat.EMAIL.name().equals(constraintValue)) { - queryWhereClauses.add("nfp.emailEnabled = 1"); - } - } - } - - private void handleIsEnabledFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) - { - // We only check first constraint: if there's more they are either redundant or contradictory. - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { - queryWhereClauses.add("nfp.enabled = 1"); - } else { - queryWhereClauses.add("nfp.enabled = 0"); - } - } - - private void handleEventTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, - FiltersHQLQuery result) - { - // We authorize only a single constraint here - // FIXME: Actually maybe we should allow specifying multiple equals constraints? - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - if (NotificationCustomFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( - constraint.getValue())) - { - queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); - } else { - queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); - result.bindings.put("eventTypes", queryParameter); - } - } - } - - private void handleLocationFilter(LiveDataQuery.Filter locationFilter, List queryWhereClauses, - FiltersHQLQuery result) - { - List clauses = new ArrayList<>(); - int clauseCounter = 0; - String clauseValue = "(nfp.pageOnly like :constraint_%1$s or nfp.page like :constraint_%1$s or " - + "nfp.wiki like :constraint_%1$s or nfp.user like :constraint_%1$s)"; - String constraintName = "constraint_%s"; - for (LiveDataQuery.Constraint constraint : locationFilter.getConstraints()) { - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - clauses.add(String.format("(nfp.pageOnly = :constraint_%1$s or nfp.page = :constraint_%1$s or " - + "nfp.wiki = :constraint_%1$s or nfp.user = :constraint_%1$s)", clauseCounter)); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.literal(String.valueOf(constraint.getValue())); - result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); - } else if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { - clauses.add(String.format(clauseValue, clauseCounter)); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.literal(String.valueOf(constraint.getValue())).anyChars(); - result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); - } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { - clauses.add(String.format(clauseValue, clauseCounter)); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); - result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); - } - clauseCounter++; - } - if (!clauses.isEmpty()) { - queryWhereClauses.add(buildQueryClause(locationFilter, clauses)); - } - } - - private String buildQueryClause(LiveDataQuery.Filter filter, List clauses) - { - StringBuilder result = new StringBuilder("("); - String operatorAppender; - if (filter.isMatchAll()) { - operatorAppender = AND; - } else { - operatorAppender = " or "; - } - Iterator iterator = clauses.iterator(); - while (iterator.hasNext()) { - result.append(iterator.next()); - if (iterator.hasNext()) { - result.append(operatorAppender); - } - } - result.append(")"); - return result.toString(); - } - - private String handleSortEntries(List sortEntries) - { - List clauses = new ArrayList<>(); - for (LiveDataQuery.SortEntry sortEntry : sortEntries) { - String sortOperator = "asc"; - if (sortEntry.isDescending()) { - sortOperator = "desc"; - } - switch (sortEntry.getProperty()) { - case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> - clauses.add(String.format("nfp.enabled %s", sortOperator)); - - case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { - if (sortEntry.isDescending()) { - clauses.add("nfp.alertEnabled desc, nfp.emailEnabled asc"); - } else { - clauses.add("nfp.alertEnabled asc, nfp.emailEnabled desc"); - } - } - - case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, - NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { - if (sortEntry.isDescending()) { - clauses.add("nfp.user desc, nfp.wiki desc, nfp.page desc, nfp.pageOnly desc"); - } else { - clauses.add("nfp.pageOnly asc, nfp.page asc, nfp.wiki asc, nfp.user asc"); - } - } - - case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> - clauses.add(String.format("nfp.filterType %s", sortOperator)); - - case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> - clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); - - default -> { - } - } - } - if (!clauses.isEmpty()) { - StringBuilder result = new StringBuilder(" order by "); - Iterator iterator = clauses.iterator(); - while (iterator.hasNext()) { - result.append(iterator.next()); - if (iterator.hasNext()) { - result.append(", "); - } - } - return result.toString(); - } else { - return ""; - } - } - @Override public LiveData get(LiveDataQuery query) throws LiveDataException { if (query.getOffset() > Integer.MAX_VALUE) { throw new LiveDataException("Currently only integer offsets are supported."); } - Map 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)); - EntityReference ownerReference; - if (WIKI_SOURCE_PARAMETER.equals(target)) { - ownerReference = - this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER)), - EntityType.WIKI); - } else { - ownerReference = this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(target)), - EntityType.DOCUMENT); - } - XWikiContext context = this.contextProvider.get(); - if (!this.contextualAuthorizationManager.hasAccess(Right.ADMIN) - && !ownerReference.equals(context.getUserReference())) { - throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); - } - String serializedOwner = this.entityReferenceSerializer.serialize(ownerReference); + TargetInformation targetInformation = getTargetInformation(query); + String serializedOwner = this.entityReferenceSerializer.serialize(targetInformation.ownerReference); + WikiReference wikiReference = + (WikiReference) targetInformation.ownerReference.extractReference(EntityType.WIKI); LiveData liveData = new LiveData(); - String baseQuery = "select nfp from DefaultNotificationFilterPreference nfp where owner = :owner"; - Optional optionalFiltersHQLQuery = handleFilter(query.getFilters()); - if (optionalFiltersHQLQuery.isPresent()) { - baseQuery += optionalFiltersHQLQuery.get().whereClause; - } - baseQuery += handleSortEntries(query.getSort()); try { - Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL) - .bindValue("owner", serializedOwner) - .setWiki(ownerReference.extractReference(EntityType.WIKI).getName()); - if (optionalFiltersHQLQuery.isPresent()) { - for (Map.Entry binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { - hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); - } - } - List notificationFilterPreferences = hqlQuery - .setLimit(query.getLimit()) - .setOffset(query.getOffset().intValue()) - .execute(); - liveData.setCount(notificationFilterPreferences.size()); + long filterCount = this.queryHelper.countTotalFilters(serializedOwner, wikiReference); + List filterPreferences = + this.queryHelper.getFilterPreferences(query, serializedOwner, wikiReference); + liveData.setCount(filterCount); List> entries = liveData.getEntries(); - for (NotificationFilterPreference notificationFilterPreference : notificationFilterPreferences) { + for (NotificationFilterPreference notificationFilterPreference : filterPreferences) { entries.add(getPreferenceInformation(notificationFilterPreference)); } } catch (QueryException | NotificationException e) { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java new file mode 100644 index 000000000000..c053758deaec --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -0,0 +1,326 @@ +/* + * 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.custom; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataQuery; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.NotificationFilterPreference; +import org.xwiki.notifications.filters.NotificationFilterType; +import org.xwiki.query.Query; +import org.xwiki.query.QueryException; +import org.xwiki.query.QueryFilter; +import org.xwiki.query.QueryManager; +import org.xwiki.query.internal.DefaultQueryParameter; + +/** + * Helper to perform DB queries for retrieving notification custom filters with live data filters and sort. + * TODO: maybe this could be improved with some APIs to put in NotificationFilterPreferenceStore. + * + * @version $Id$ + * @since 16.2.0RC1 + */ +@Component(roles = NotificationCustomFiltersQueryHelper.class) +@Singleton +public class NotificationCustomFiltersQueryHelper +{ + private static final String BASE_QUERY = "select nfp from DefaultNotificationFilterPreference nfp " + + "where owner = :owner"; + private static final String OWNER_BINDING = "owner"; + private static final String STARTS_WITH_OPERATOR = "startsWith"; + private static final String CONTAINS_OPERATOR = "contains"; + private static final String EQUALS_OPERATOR = "equals"; + private static final String AND = " and "; + + @Inject + private QueryManager queryManager; + + @Inject + @Named("count") + private QueryFilter countQueryFilter; + + private static final class FiltersHQLQuery + { + private String whereClause; + private final Map bindings = new LinkedHashMap<>(); + } + + private Optional handleFilter(List queryFilters) + { + FiltersHQLQuery result = new FiltersHQLQuery(); + List queryWhereClauses = new ArrayList<>(); + for (LiveDataQuery.Filter queryFilter : queryFilters) { + switch (queryFilter.getProperty()) { + case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> + this.handleIsEnabledFilter(queryFilter, queryWhereClauses); + + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> + this.handleNotificationFormatsFilter(queryFilter, queryWhereClauses); + + case NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> + this.handleScopeFilter(queryFilter, queryWhereClauses); + + case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> + this.handleFilterTypeFilter(queryFilter, queryWhereClauses, result); + + case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> + this.handleLocationFilter(queryFilter, queryWhereClauses, result); + + case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> + this.handleEventTypeFilter(queryFilter, queryWhereClauses, result); + + default -> { + } + } + } + if (!queryWhereClauses.isEmpty()) { + result.whereClause = queryWhereClauses.stream().collect(Collectors.joining(AND, AND, "")); + return Optional.of(result); + } else { + return Optional.empty(); + } + } + + private void handleFilterTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, + FiltersHQLQuery result) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + queryWhereClauses.add("nfp.filterType = :filterType"); + result.bindings.put("filterType", + NotificationFilterType.valueOf(String.valueOf(constraint.getValue()))); + } + } + + private void handleScopeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { + NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = + NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( + String.valueOf(constraint.getValue())); + queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); + } + } + + private void handleNotificationFormatsFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + String constraintValue = String.valueOf(constraint.getValue()); + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isEmpty(constraintValue)) { + if (NotificationFormat.ALERT.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.alertEnabled = 1"); + } + if (NotificationFormat.EMAIL.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.emailEnabled = 1"); + } + } + } + + private void handleIsEnabledFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) + { + // We only check first constraint: if there's more they are either redundant or contradictory. + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { + queryWhereClauses.add("nfp.enabled = 1"); + } else { + queryWhereClauses.add("nfp.enabled = 0"); + } + } + + private void handleEventTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, + FiltersHQLQuery result) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + if (NotificationCustomFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( + constraint.getValue())) + { + queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); + } else { + queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put("eventTypes", queryParameter); + } + } + } + + private void handleLocationFilter(LiveDataQuery.Filter locationFilter, List queryWhereClauses, + FiltersHQLQuery result) + { + List clauses = new ArrayList<>(); + int clauseCounter = 0; + + // for searching in location we actually need to look in 4 columns in DB, so we reuse same constraint binding + // in those 4 columns, and since we might have multiple constraints we use a counter to name those bindings + String clauseValue = "(nfp.pageOnly like :constraint_%1$s or nfp.page like :constraint_%1$s or " + + "nfp.wiki like :constraint_%1$s or nfp.user like :constraint_%1$s)"; + String constraintName = "constraint_%s"; + + for (LiveDataQuery.Constraint constraint : locationFilter.getConstraints()) { + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format("(nfp.pageOnly = :constraint_%1$s or nfp.page = :constraint_%1$s or " + + "nfp.wiki = :constraint_%1$s or nfp.user = :constraint_%1$s)", clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.literal(String.valueOf(constraint.getValue())); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } else if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format(clauseValue, clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format(clauseValue, clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } + clauseCounter++; + } + if (!clauses.isEmpty()) { + queryWhereClauses.add(buildQueryClause(locationFilter, clauses)); + } + } + + private String buildQueryClause(LiveDataQuery.Filter filter, List clauses) + { + String operatorAppender; + if (filter.isMatchAll()) { + operatorAppender = AND; + } else { + operatorAppender = " or "; + } + return clauses.stream().collect(Collectors.joining(operatorAppender, "(", ")")); + } + + /** + * Count the total number of filter preferences for given owner on given wiki. + * @param owner the owner for which to count all filter preferences + * @param wikiReference the wiki where to count all filter preferences + * @return the total number of filter preferences + * @throws QueryException in case of problem to perform the query + */ + public long countTotalFilters(String owner, WikiReference wikiReference) throws QueryException + { + return this.queryManager.createQuery(BASE_QUERY, Query.HQL) + .bindValue(OWNER_BINDING, owner) + .setWiki(wikiReference.getName()) + .addFilter(this.countQueryFilter) + .execute() + .get(0); + } + + private String handleSortEntries(List sortEntries) + { + List clauses = new ArrayList<>(); + for (LiveDataQuery.SortEntry sortEntry : sortEntries) { + String sortOperator = "asc"; + if (sortEntry.isDescending()) { + sortOperator = "desc"; + } + switch (sortEntry.getProperty()) { + case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> + clauses.add(String.format("nfp.enabled %s", sortOperator)); + + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { + // We use on purpose asc/desc in combination to ensure having both formats in the middle of the data + // since it's the most common: by doing that we'll have [only alerts, both formats, only emails] + if (sortEntry.isDescending()) { + clauses.add("nfp.emailEnabled asc, nfp.alertEnabled desc"); + } else { + clauses.add("nfp.alertEnabled asc, nfp.emailEnabled desc"); + } + } + + case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, + NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> + clauses.add(String.format("nfp.user %1$s, nfp.wiki %1$s, nfp.page %1$s, nfp.pageOnly %1$s", + sortOperator)); + + case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> + clauses.add(String.format("nfp.filterType %s", sortOperator)); + + case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> + clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); + + default -> { + } + } + } + if (!clauses.isEmpty()) { + return clauses.stream().collect(Collectors.joining(", ", " order by ", "")); + } else { + return ""; + } + } + + /** + * Retrieve all filter preferences matching the live data query for the given owner on the given wiki. + * + * @param query the livedata query containing filters and sort criteria + * @param owner the owner of the filter preferences to retrieve + * @param wikiReference the wiki where to retrieve the filter preferences + * @return the list of filter preferences + * @throws QueryException in case of problem to perform the query + */ + public List getFilterPreferences(LiveDataQuery query, String owner, + WikiReference wikiReference) throws QueryException + { + String baseQuery = BASE_QUERY; + Optional optionalFiltersHQLQuery = handleFilter(query.getFilters()); + if (optionalFiltersHQLQuery.isPresent()) { + baseQuery += optionalFiltersHQLQuery.get().whereClause; + } + baseQuery += handleSortEntries(query.getSort()); + Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL) + .bindValue(OWNER_BINDING, owner) + .setWiki(wikiReference.getName()); + if (optionalFiltersHQLQuery.isPresent()) { + for (Map.Entry binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { + hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); + } + } + return hqlQuery + .setLimit(query.getLimit()) + .setOffset(query.getOffset().intValue()) + .execute(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java index d70de002156d..f7ebdf1982ef 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java @@ -50,7 +50,6 @@ public class NotificationSystemFiltersLiveDataConfigurationProvider implements P static final String IS_ENABLED_FIELD = "isEnabled"; private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.system.table."; private static final String STRING_TYPE = "String"; - private static final String HTML_DISPLAYER = "html"; @Inject private ContextualLocalizationManager l10n; @@ -80,18 +79,21 @@ public LiveDataConfiguration get() return input; } - private LiveDataPropertyDescriptor getNameDescriptor() + private void setDescriptorValues(LiveDataPropertyDescriptor descriptor) { - LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NAME_FIELD)); - descriptor.setId(NAME_FIELD); - descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(false); descriptor.setFilterable(false); + } + private LiveDataPropertyDescriptor getNameDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NAME_FIELD)); + descriptor.setId(NAME_FIELD); + descriptor.setType(STRING_TYPE); + this.setDescriptorValues(descriptor); return descriptor; } @@ -101,12 +103,7 @@ private LiveDataPropertyDescriptor getDescriptionDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + DESCRIPTION_FIELD)); descriptor.setId(DESCRIPTION_FIELD); descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); - descriptor.setVisible(true); - descriptor.setEditable(false); - descriptor.setSortable(false); - descriptor.setFilterable(false); - + this.setDescriptorValues(descriptor); return descriptor; } @@ -116,12 +113,8 @@ 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.setVisible(true); - descriptor.setEditable(false); - descriptor.setSortable(false); - descriptor.setFilterable(false); - + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("staticList")); + this.setDescriptorValues(descriptor); return descriptor; } @@ -130,16 +123,9 @@ 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.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("toggle")); - descriptor.setVisible(true); - descriptor.setEditable(false); - descriptor.setSortable(false); - descriptor.setFilterable(false); - + this.setDescriptorValues(descriptor); return descriptor; } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index d5de17701e19..b7c16bcb7cc8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -23,11 +23,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Provider; import javax.inject.Singleton; import org.xwiki.component.annotation.Component; @@ -36,21 +34,15 @@ import org.xwiki.livedata.LiveDataException; import org.xwiki.livedata.LiveDataQuery; import org.xwiki.model.reference.DocumentReference; -import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationException; -import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.internal.FilterPreferencesModelBridge; import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; import org.xwiki.notifications.filters.internal.ToggleableNotificationFilterActivation; -import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; -import org.xwiki.security.authorization.ContextualAuthorizationManager; -import org.xwiki.security.authorization.Right; - -import com.xpn.xwiki.XWikiContext; +import org.xwiki.notifications.filters.internal.livedata.AbstractNotificationFilterLiveDataEntryStore; /** * Dedicated {@link LiveDataEntryStore} for the {@link NotificationSystemFiltersLiveDataSource}. @@ -61,32 +53,17 @@ @Component @Singleton @Named(NotificationSystemFiltersLiveDataSource.NAME) -public class NotificationSystemFiltersLiveDataEntryStore implements LiveDataEntryStore +public class NotificationSystemFiltersLiveDataEntryStore extends AbstractNotificationFilterLiveDataEntryStore { - 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."; private static final LocalDocumentReference NOTIFICATION_ADMINISTRATION_REF = new LocalDocumentReference(List.of("XWiki", "Notifications", "Code"), "NotificationAdministration"); - @Inject - private Provider contextProvider; - @Inject private NotificationFilterManager notificationFilterManager; @Inject private FilterPreferencesModelBridge filterPreferencesModelBridge; - @Inject - private DocumentReferenceResolver documentReferenceResolver; - - @Inject - private NotificationFilterLiveDataTranslationHelper translationHelper; - - @Inject - private ContextualAuthorizationManager contextualAuthorizationManager; - @Override public Optional> get(Object entryId) throws LiveDataException { @@ -105,13 +82,12 @@ private Map getPreferencesInformation(ToggleableNotificationFilt this.translationHelper.getTranslationWithPrefix("notifications.filters.description.", notificationFilter.getName()), NotificationSystemFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, - this.displayNotificationFormats(notificationFilter), - NotificationSystemFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, - this.displayIsEnabled(notificationFilter, filterActivation), + displayNotificationFormats(notificationFilter.getFormats()), + // We don't need to return isEnabled, but only the metadata defined below. "isEnabled_data", - this.displayIsEnabledData(notificationFilter, filterActivation), + displayIsEnabledData(notificationFilter, filterActivation), "isEnabled_checked", - this.isEnabled(notificationFilter, filterActivation) + isEnabled(notificationFilter, filterActivation) ); } @@ -133,98 +109,56 @@ private Map displayIsEnabledData(NotificationFilter notification private boolean isEnabled(ToggleableNotificationFilter notificationFilter, ToggleableNotificationFilterActivation filterActivation) { - return (filterActivation != null && filterActivation.isEnabled()) || - (filterActivation == null && notificationFilter.isEnabledByDefault()); - } - - private String displayIsEnabled(ToggleableNotificationFilter notificationFilter, - ToggleableNotificationFilterActivation filterActivation) - { - String checked = (this.isEnabled(notificationFilter, filterActivation)) ? "checked=\"checked\"" : ""; - String html = ""; - return String.format(html, notificationFilter.getName(), getObjectNumber(filterActivation), checked); - } - - private String displayNotificationFormats(ToggleableNotificationFilter filter) - { - StringBuilder result = new StringBuilder("
    "); - - for (NotificationFormat notificationFormat : filter.getFormats()) { - result.append("
  • "); - result.append(this.translationHelper.getFormatTranslation(notificationFormat)); - result.append("
  • "); - } - result.append("
"); - - return result.toString(); + return (filterActivation != null && filterActivation.isEnabled()) + || (filterActivation == null && notificationFilter.isEnabledByDefault()); } @Override public LiveData get(LiveDataQuery query) throws LiveDataException { - if (query.getOffset() > Integer.MAX_VALUE) { - throw new LiveDataException("Currently only integer offsets are supported."); - } - Map 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)); - List filters = null; + TargetInformation targetInformation = getTargetInformation(query); Map filtersActivations = null; - boolean isAuthorized = false; + List allFilters = null; try { - if (WIKI_SOURCE_PARAMETER.equals(target)) { - WikiReference wikiReference = - new WikiReference(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER))); - if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN, wikiReference)) { - isAuthorized = true; - filters = this.notificationFilterManager.getAllFilters(wikiReference) + if (targetInformation.isWikiTarget) { + WikiReference wikiReference = (WikiReference) targetInformation.ownerReference; + allFilters = + this.notificationFilterManager.getAllFilters(wikiReference) .stream() .filter(filter -> filter instanceof ToggleableNotificationFilter) .map(item -> (ToggleableNotificationFilter) item) - .collect(Collectors.toList()); - filtersActivations = - this.filterPreferencesModelBridge.getToggleableFilterActivations( - new DocumentReference(NOTIFICATION_ADMINISTRATION_REF, wikiReference)); - } + .toList(); + filtersActivations = + this.filterPreferencesModelBridge.getToggleableFilterActivations( + new DocumentReference(NOTIFICATION_ADMINISTRATION_REF, wikiReference)); } else { - XWikiContext context = this.contextProvider.get(); - String targetValue = String.valueOf(sourceParameters.get(target)); - DocumentReference userDoc = - this.documentReferenceResolver.resolve(targetValue); - if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN) - || context.getUserReference().equals(userDoc)) { - isAuthorized = true; - filters = this.notificationFilterManager.getAllFilters(userDoc, false) + DocumentReference userDoc = (DocumentReference) targetInformation.ownerReference; + allFilters = this.notificationFilterManager.getAllFilters(userDoc, false) .stream() .filter(filter -> filter instanceof ToggleableNotificationFilter) .map(item -> (ToggleableNotificationFilter) item) - .collect(Collectors.toList()); - filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations(userDoc); - } + .toList(); + filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations(userDoc); } } catch (NotificationException e) { throw new LiveDataException("Error when getting list of filters", e); } - if (!isAuthorized) { - throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); - } - filters.sort(Comparator.comparing(NotificationFilter::getName)); + int totalFilters = allFilters.size(); + List filters = allFilters + .stream() + .sorted(Comparator.comparing(NotificationFilter::getName)) + .skip(query.getOffset()) + .limit(query.getLimit()) + .toList(); LiveData liveData = new LiveData(); - int offset = query.getOffset().intValue(); - if (offset < filters.size()) { - List> entries = liveData.getEntries(); - for (int i = offset; i < Math.min(filters.size(), offset + query.getLimit()); i++) { - ToggleableNotificationFilter notificationFilter = filters.get(i); - entries.add(getPreferencesInformation(notificationFilter, - filtersActivations.get(notificationFilter.getName()))); - } - liveData.setCount(entries.size()); + List> entries = liveData.getEntries(); + for (ToggleableNotificationFilter notificationFilter : filters) { + entries.add(getPreferencesInformation(notificationFilter, + filtersActivations.get(notificationFilter.getName()))); } + liveData.setCount(totalFilters); return liveData; } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt index d236c7ff52db..0f2cb49877b7 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt @@ -4,6 +4,7 @@ org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFilte org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataEntryStore org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataPropertyDescriptorStore org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataSource +org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersQueryHelper org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataConfigurationProvider org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataConfigurationResolver org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataEntryStore diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index a2af30cbd19a..4bad4fe6febf 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -470,6 +470,7 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', ], function ($, xm, Vue, notifications, l10n) { Vue.component('DisplayerToggle', notifications.DisplayerToggle); Vue.component('DisplayerScope', notifications.DisplayerScope); + Vue.component('DisplayerStaticList', notifications.DisplayerStaticList); // Globals var serviceReference = XWiki.Model.resolve('XWiki.Notifications.Code.NotificationPreferenceService', diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml index e2fd864295c5..098d83e97a9e 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml @@ -136,6 +136,7 @@ require(['jquery', 'xwiki-l10n!notifications-system-filters-preferences-macro', 'vue', 'xwiki-platform-notifications-webjar'], function ($, l10n, Vue, notifications) { Vue.component('DisplayerToggle', notifications.DisplayerToggle) + Vue.component('DisplayerStaticList', notifications.DisplayerStaticList); function save(checked, filterName, objectNumber, docURL) { return new Promise((resolve) => { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue new file mode 100644 index 000000000000..3d31adb65306 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue @@ -0,0 +1,64 @@ + + + + + + \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js index edc65b39e77a..b6b669eb2aa8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js @@ -19,6 +19,7 @@ */ import DisplayerToggle from "./components/DisplayerToggle.vue"; import DisplayerScope from "./components/DisplayerScope.vue"; +import DisplayerStaticList from "./components/DisplayerStaticList.vue"; // Export the elements that are expected to be imported from other modules. -export {DisplayerToggle,DisplayerScope} \ No newline at end of file +export {DisplayerToggle,DisplayerScope,DisplayerStaticList} \ No newline at end of file From 30a58761aab52d34d150c03213d51a6aa8f0322f Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 13 Mar 2024 14:37:24 +0100 Subject: [PATCH 13/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Fix some bugs --- .../NotificationCustomFiltersLiveDataEntryStore.java | 2 +- .../custom/NotificationCustomFiltersQueryHelper.java | 10 ++-------- .../NotificationSystemFiltersLiveDataEntryStore.java | 4 ++-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index 9031d7bd8fad..9e147c595a5e 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -233,7 +233,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException TargetInformation targetInformation = getTargetInformation(query); String serializedOwner = this.entityReferenceSerializer.serialize(targetInformation.ownerReference); WikiReference wikiReference = - (WikiReference) targetInformation.ownerReference.extractReference(EntityType.WIKI); + new WikiReference(targetInformation.ownerReference.extractReference(EntityType.WIKI)); LiveData liveData = new LiveData(); try { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java index c053758deaec..3432ebb3ab94 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -28,7 +28,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; @@ -40,7 +39,6 @@ import org.xwiki.notifications.filters.NotificationFilterType; import org.xwiki.query.Query; import org.xwiki.query.QueryException; -import org.xwiki.query.QueryFilter; import org.xwiki.query.QueryManager; import org.xwiki.query.internal.DefaultQueryParameter; @@ -66,10 +64,6 @@ public class NotificationCustomFiltersQueryHelper @Inject private QueryManager queryManager; - @Inject - @Named("count") - private QueryFilter countQueryFilter; - private static final class FiltersHQLQuery { private String whereClause; @@ -240,10 +234,10 @@ private String buildQueryClause(LiveDataQuery.Filter filter, List clause */ public long countTotalFilters(String owner, WikiReference wikiReference) throws QueryException { - return this.queryManager.createQuery(BASE_QUERY, Query.HQL) + return this.queryManager.createQuery("select count(nfp.id) " + + "from DefaultNotificationFilterPreference nfp where owner = :owner", Query.HQL) .bindValue(OWNER_BINDING, owner) .setWiki(wikiReference.getName()) - .addFilter(this.countQueryFilter) .execute() .get(0); } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index b7c16bcb7cc8..eabb3fa4cb6a 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -121,7 +121,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException List allFilters = null; try { if (targetInformation.isWikiTarget) { - WikiReference wikiReference = (WikiReference) targetInformation.ownerReference; + WikiReference wikiReference = new WikiReference(targetInformation.ownerReference); allFilters = this.notificationFilterManager.getAllFilters(wikiReference) .stream() @@ -132,7 +132,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException this.filterPreferencesModelBridge.getToggleableFilterActivations( new DocumentReference(NOTIFICATION_ADMINISTRATION_REF, wikiReference)); } else { - DocumentReference userDoc = (DocumentReference) targetInformation.ownerReference; + DocumentReference userDoc = new DocumentReference(targetInformation.ownerReference); allFilters = this.notificationFilterManager.getAllFilters(userDoc, false) .stream() .filter(filter -> filter instanceof ToggleableNotificationFilter) From a52c1fafdc2cd174556d81ff7a5ba04470710573 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 13 Mar 2024 15:30:18 +0100 Subject: [PATCH 14/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Fix some other bugs --- ...cationFilterLiveDataTranslationHelper.java | 7 ++- ...cationCustomFiltersLiveDataEntryStore.java | 2 +- .../NotificationCustomFiltersQueryHelper.java | 48 +++++++++++-------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index c6f53fa54dd1..c7e80f2fb177 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -114,15 +114,18 @@ public String getAllEventTypesTranslation() */ public String getEventTypeTranslation(String eventType) { + String result = eventType; try { RecordableEventDescriptor descriptor = this.recordableEventDescriptorManager.getDescriptorForEventType(eventType, true); - return getTranslationWithFallback(descriptor.getDescription()); + if (descriptor != null) { + result = getTranslationWithFallback(descriptor.getDescription()); + } } catch (EventStreamException e) { this.logger.error("Error while getting description for event type [{}] falling back on event name", eventType, e); - return eventType; } + return result; } /** diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index 9e147c595a5e..615704c486e0 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -237,7 +237,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException LiveData liveData = new LiveData(); try { - long filterCount = this.queryHelper.countTotalFilters(serializedOwner, wikiReference); + long filterCount = this.queryHelper.countTotalFilters(query, serializedOwner, wikiReference); List filterPreferences = this.queryHelper.getFilterPreferences(query, serializedOwner, wikiReference); liveData.setCount(filterCount); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java index 3432ebb3ab94..a806ad6e1fac 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -225,19 +225,41 @@ private String buildQueryClause(LiveDataQuery.Filter filter, List clause return clauses.stream().collect(Collectors.joining(operatorAppender, "(", ")")); } + private Query getHQLQuery(LiveDataQuery query, boolean isCount, String owner, WikiReference wikiReference) + throws QueryException + { + String baseQuery = (isCount) ? "select count(nfp.id) " + + "from DefaultNotificationFilterPreference nfp where owner = :owner" : BASE_QUERY; + Optional optionalFiltersHQLQuery = handleFilter(query.getFilters()); + if (optionalFiltersHQLQuery.isPresent()) { + baseQuery += optionalFiltersHQLQuery.get().whereClause; + } + if (!isCount) { + baseQuery += handleSortEntries(query.getSort()); + } + Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL) + .bindValue(OWNER_BINDING, owner) + .setWiki(wikiReference.getName()); + if (optionalFiltersHQLQuery.isPresent()) { + for (Map.Entry binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { + hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); + } + } + return hqlQuery; + } + /** * Count the total number of filter preferences for given owner on given wiki. + * + * @param query the query to use for applying the filters * @param owner the owner for which to count all filter preferences * @param wikiReference the wiki where to count all filter preferences * @return the total number of filter preferences * @throws QueryException in case of problem to perform the query */ - public long countTotalFilters(String owner, WikiReference wikiReference) throws QueryException + public long countTotalFilters(LiveDataQuery query, String owner, WikiReference wikiReference) throws QueryException { - return this.queryManager.createQuery("select count(nfp.id) " - + "from DefaultNotificationFilterPreference nfp where owner = :owner", Query.HQL) - .bindValue(OWNER_BINDING, owner) - .setWiki(wikiReference.getName()) + return getHQLQuery(query, true, owner, wikiReference) .execute() .get(0); } @@ -298,21 +320,7 @@ private String handleSortEntries(List sortEntries) public List getFilterPreferences(LiveDataQuery query, String owner, WikiReference wikiReference) throws QueryException { - String baseQuery = BASE_QUERY; - Optional optionalFiltersHQLQuery = handleFilter(query.getFilters()); - if (optionalFiltersHQLQuery.isPresent()) { - baseQuery += optionalFiltersHQLQuery.get().whereClause; - } - baseQuery += handleSortEntries(query.getSort()); - Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL) - .bindValue(OWNER_BINDING, owner) - .setWiki(wikiReference.getName()); - if (optionalFiltersHQLQuery.isPresent()) { - for (Map.Entry binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { - hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); - } - } - return hqlQuery + return getHQLQuery(query, false, owner, wikiReference) .setLimit(query.getLimit()) .setOffset(query.getOffset().intValue()) .execute(); From 3c3b57463b13075874dc2e8dce934e1515d8d94e Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 13 Mar 2024 15:45:42 +0100 Subject: [PATCH 15/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Mark one of the translation as deprecated --- .../main/resources/XWiki/Notifications/Code/Translations.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml index 3f583784980f..b1b77042746f 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml @@ -104,8 +104,6 @@ notifications.settings.filters.preferences.system.table.isEnabled=Is enabled? notifications.settings.filters.preferences.custom.title=Custom Filters notifications.settings.filters.preferences.custom.hint=Create filters to control the notifications you want to receive. ## Header definition for notification filter preferences -notifications.settings.filters.preferences.custom.table.name=Location -notifications.settings.filters.preferences.custom.table.location=Location notifications.settings.filters.preferences.custom.table.scope=Scope notifications.settings.filters.preferences.custom.table.location=Location notifications.settings.filters.preferences.custom.table.filterType=Filter Action @@ -273,6 +271,7 @@ notifications.filters.type.exclusive=Exclusive ## until 16.2.0RC1 notifications.settings.filters.preferences.custom.table.description=This table lists every custom filter registered for the given user or current wiki. notifications.settings.filters.preferences.system.table.description=This table lists every system filter registered in the wiki. +notifications.settings.filters.preferences.custom.table.name=Location ## Used to indicate where deprecated keys end #@deprecatedend From 4b102eb08bce4f2ad20cc9e454c6e70bb8e18069 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 13 Mar 2024 15:51:44 +0100 Subject: [PATCH 16/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Fix bad translations for enabled filter --- ...ficationsCustomFiltersPreferencesMacro.xml | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index 4bad4fe6febf..defc46da287c 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -128,17 +128,19 @@ 'use strict'; define('notifications-custom-filters-preferences-macro-translation-keys', { - prefix: 'notifications.', + prefix: '', keys: [ - 'settings.saving', - 'settings.saved', - 'settings.savingfailed', - 'filters.preferences.setEnabled.inProgress', - 'filters.preferences.setEnabled.done', - 'filters.preferences.setEnabled.error', - 'filters.preferences.delete.inProgress', - 'filters.preferences.delete.done', - 'filters.preferences.delete.error' + 'notifications.settings.saving', + 'notifications.settings.saved', + 'notifications.settings.savingfailed', + 'notifications.filters.preferences.setEnabled.inProgress', + 'notifications.filters.preferences.setEnabled.done', + 'notifications.filters.preferences.setEnabled.error', + 'notifications.filters.preferences.delete.inProgress', + 'notifications.filters.preferences.delete.done', + 'notifications.filters.preferences.delete.error', + 'livedata.displayer.boolean.true', + 'livedata.displayer.boolean.false' ] }); From ed23e998accc3cd23579cd9c1ef07d391e2c855a Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 13 Mar 2024 17:23:10 +0100 Subject: [PATCH 17/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Rollback changes from previous commit as it apparently doesn't work... * Fix issue with sort of scope --- .../NotificationCustomFiltersQueryHelper.java | 67 +++++++++++-------- ...ficationsCustomFiltersPreferencesMacro.xml | 22 +++--- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java index a806ad6e1fac..306f8ed39561 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -32,6 +32,7 @@ import org.apache.commons.lang3.StringUtils; import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataException; import org.xwiki.livedata.LiveDataQuery; import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationFormat; @@ -226,7 +227,7 @@ private String buildQueryClause(LiveDataQuery.Filter filter, List clause } private Query getHQLQuery(LiveDataQuery query, boolean isCount, String owner, WikiReference wikiReference) - throws QueryException + throws QueryException, LiveDataException { String baseQuery = (isCount) ? "select count(nfp.id) " + "from DefaultNotificationFilterPreference nfp where owner = :owner" : BASE_QUERY; @@ -256,50 +257,41 @@ private Query getHQLQuery(LiveDataQuery query, boolean isCount, String owner, Wi * @param wikiReference the wiki where to count all filter preferences * @return the total number of filter preferences * @throws QueryException in case of problem to perform the query + * @throws LiveDataException in case of problem with the livedata query */ - public long countTotalFilters(LiveDataQuery query, String owner, WikiReference wikiReference) throws QueryException + public long countTotalFilters(LiveDataQuery query, String owner, WikiReference wikiReference) + throws QueryException, LiveDataException { return getHQLQuery(query, true, owner, wikiReference) .execute() .get(0); } - private String handleSortEntries(List sortEntries) + private String handleSortEntries(List sortEntries) throws LiveDataException { List clauses = new ArrayList<>(); for (LiveDataQuery.SortEntry sortEntry : sortEntries) { - String sortOperator = "asc"; - if (sortEntry.isDescending()) { - sortOperator = "desc"; - } - switch (sortEntry.getProperty()) { + String sortOperator = (sortEntry.isDescending()) ? "desc" : "asc"; + String clause = switch (sortEntry.getProperty()) { case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> - clauses.add(String.format("nfp.enabled %s", sortOperator)); - - case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { - // We use on purpose asc/desc in combination to ensure having both formats in the middle of the data - // since it's the most common: by doing that we'll have [only alerts, both formats, only emails] - if (sortEntry.isDescending()) { - clauses.add("nfp.emailEnabled asc, nfp.alertEnabled desc"); - } else { - clauses.add("nfp.alertEnabled asc, nfp.emailEnabled desc"); - } - } + String.format("nfp.enabled %s", sortOperator); + + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> + handleNotificationFormatSort(sortEntry); case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> - clauses.add(String.format("nfp.user %1$s, nfp.wiki %1$s, nfp.page %1$s, nfp.pageOnly %1$s", - sortOperator)); + handleLocationSort(sortEntry); case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> - clauses.add(String.format("nfp.filterType %s", sortOperator)); + String.format("nfp.filterType %s", sortOperator); case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> - clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); + String.format("nfp.allEventTypes %s", sortOperator); - default -> { - } - } + default -> throw new LiveDataException("Unexpected sort value: " + sortEntry.getProperty()); + }; + clauses.add(clause); } if (!clauses.isEmpty()) { return clauses.stream().collect(Collectors.joining(", ", " order by ", "")); @@ -308,6 +300,26 @@ private String handleSortEntries(List sortEntries) } } + private String handleLocationSort(LiveDataQuery.SortEntry sortEntry) + { + if (sortEntry.isDescending()) { + return "nfp.user desc, nfp.wiki desc, nfp.page desc, nfp.pageOnly desc"; + } else { + return "nfp.pageOnly asc, nfp.page asc, nfp.wiki asc, nfp.user asc"; + } + } + + private String handleNotificationFormatSort(LiveDataQuery.SortEntry sortEntry) + { + // We use on purpose asc/desc in combination to ensure having both formats in the middle of the data + // since it's the most common: by doing that we'll have [only alerts, both formats, only emails] + if (sortEntry.isDescending()) { + return "nfp.emailEnabled asc, nfp.alertEnabled desc"; + } else { + return "nfp.alertEnabled asc, nfp.emailEnabled desc"; + } + } + /** * Retrieve all filter preferences matching the live data query for the given owner on the given wiki. * @@ -316,9 +328,10 @@ private String handleSortEntries(List sortEntries) * @param wikiReference the wiki where to retrieve the filter preferences * @return the list of filter preferences * @throws QueryException in case of problem to perform the query + * @throws LiveDataException in case of problem with the livedata query */ public List getFilterPreferences(LiveDataQuery query, String owner, - WikiReference wikiReference) throws QueryException + WikiReference wikiReference) throws QueryException, LiveDataException { return getHQLQuery(query, false, owner, wikiReference) .setLimit(query.getLimit()) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index defc46da287c..4bad4fe6febf 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -128,19 +128,17 @@ 'use strict'; define('notifications-custom-filters-preferences-macro-translation-keys', { - prefix: '', + prefix: 'notifications.', keys: [ - 'notifications.settings.saving', - 'notifications.settings.saved', - 'notifications.settings.savingfailed', - 'notifications.filters.preferences.setEnabled.inProgress', - 'notifications.filters.preferences.setEnabled.done', - 'notifications.filters.preferences.setEnabled.error', - 'notifications.filters.preferences.delete.inProgress', - 'notifications.filters.preferences.delete.done', - 'notifications.filters.preferences.delete.error', - 'livedata.displayer.boolean.true', - 'livedata.displayer.boolean.false' + 'settings.saving', + 'settings.saved', + 'settings.savingfailed', + 'filters.preferences.setEnabled.inProgress', + 'filters.preferences.setEnabled.done', + 'filters.preferences.setEnabled.error', + 'filters.preferences.delete.inProgress', + 'filters.preferences.delete.done', + 'filters.preferences.delete.error' ] }); From 175afe2601fe9c06cd26aeda743ebbc117bc5eb1 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 14 Mar 2024 10:22:23 +0100 Subject: [PATCH 18/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Improve a bit the code of NotificationSystemFiltersLiveDataEntryStore * Start adding tests --- ...cationSystemFiltersLiveDataEntryStore.java | 40 +++---- ...onSystemFiltersLiveDataEntryStoreTest.java | 107 ++++++++++++++++++ 2 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index eabb3fa4cb6a..cf53a41c63d7 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -19,6 +19,7 @@ */ package org.xwiki.notifications.filters.internal.livedata.system; +import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -117,48 +118,43 @@ private boolean isEnabled(ToggleableNotificationFilter notificationFilter, public LiveData get(LiveDataQuery query) throws LiveDataException { TargetInformation targetInformation = getTargetInformation(query); - Map filtersActivations = null; - List allFilters = null; + Map filtersActivations; + + Collection notificationFilters; try { if (targetInformation.isWikiTarget) { WikiReference wikiReference = new WikiReference(targetInformation.ownerReference); - allFilters = - this.notificationFilterManager.getAllFilters(wikiReference) - .stream() - .filter(filter -> filter instanceof ToggleableNotificationFilter) - .map(item -> (ToggleableNotificationFilter) item) - .toList(); + notificationFilters = this.notificationFilterManager.getAllFilters(wikiReference); filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations( new DocumentReference(NOTIFICATION_ADMINISTRATION_REF, wikiReference)); } else { DocumentReference userDoc = new DocumentReference(targetInformation.ownerReference); - allFilters = this.notificationFilterManager.getAllFilters(userDoc, false) - .stream() - .filter(filter -> filter instanceof ToggleableNotificationFilter) - .map(item -> (ToggleableNotificationFilter) item) - .toList(); + notificationFilters = this.notificationFilterManager.getAllFilters(userDoc, false); filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations(userDoc); } } catch (NotificationException e) { throw new LiveDataException("Error when getting list of filters", e); } + List allFilters = notificationFilters.stream() + .filter(filter -> filter instanceof ToggleableNotificationFilter) + .map(item -> (ToggleableNotificationFilter) item) + .toList(); + int totalFilters = allFilters.size(); - List filters = allFilters + LiveData liveData = new LiveData(); + liveData.setCount(totalFilters); + List> entries = liveData.getEntries(); + + allFilters .stream() .sorted(Comparator.comparing(NotificationFilter::getName)) .skip(query.getOffset()) .limit(query.getLimit()) - .toList(); + .forEach(filter -> + entries.add(getPreferencesInformation(filter, filtersActivations.get(filter.getName())))); - LiveData liveData = new LiveData(); - List> entries = liveData.getEntries(); - for (ToggleableNotificationFilter notificationFilter : filters) { - entries.add(getPreferencesInformation(notificationFilter, - filtersActivations.get(notificationFilter.getName()))); - } - liveData.setCount(totalFilters); return liveData; } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java new file mode 100644 index 000000000000..62e5609e3cf5 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java @@ -0,0 +1,107 @@ +/* + + * 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.system; + +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Provider; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.LiveDataQuery; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.notifications.filters.NotificationFilterManager; +import org.xwiki.notifications.filters.internal.FilterPreferencesModelBridge; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; +import org.xwiki.test.junit5.mockito.ComponentTest; +import org.xwiki.test.junit5.mockito.InjectMockComponents; +import org.xwiki.test.junit5.mockito.MockComponent; + +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.test.reference.ReferenceComponentList; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link NotificationSystemFiltersLiveDataEntryStore}. + * + * @version $Id$ + * @since 16.2.0RC1 + */ +@ComponentTest +@ReferenceComponentList +class NotificationSystemFiltersLiveDataEntryStoreTest +{ + @InjectMockComponents + private NotificationSystemFiltersLiveDataEntryStore entryStore; + + @MockComponent + private NotificationFilterManager notificationFilterManager; + + @MockComponent + private FilterPreferencesModelBridge filterPreferencesModelBridge; + + @MockComponent + private ContextualAuthorizationManager contextualAuthorizationManager; + + @MockComponent + private Provider contextProvider; + + private XWikiContext context; + + @BeforeEach + void beforeEach() + { + this.context = mock(XWikiContext.class); + when(this.contextProvider.get()).thenReturn(this.context); + } + + @Test + void getMissingTarget() + { + LiveDataQuery query = mock(LiveDataQuery.class); + LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); + when(query.getSource()).thenReturn(source); + when(source.getParameters()).thenReturn(Map.of()); + LiveDataException liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(query)); + assertEquals("The target source parameter is mandatory.", liveDataException.getMessage()); + } + + @Test + void getBadAuthorization() + { + LiveDataQuery query = mock(LiveDataQuery.class); + LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); + when(query.getSource()).thenReturn(source); + String target = "wiki"; + String wiki = "Wiki foo"; + DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "Foo"); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + when(context.getUserReference()).thenReturn(userDoc); + } +} \ No newline at end of file From 8ee00874df80302c93a07feb54be7e05860ef877 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Mon, 18 Mar 2024 14:01:28 +0100 Subject: [PATCH 19/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Provide a few tests --- ...onSystemFiltersLiveDataEntryStoreTest.java | 412 +++++++++++++++++- 1 file changed, 405 insertions(+), 7 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java index 62e5609e3cf5..16c9a18e036c 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStoreTest.java @@ -1,5 +1,4 @@ /* - * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * @@ -17,23 +16,30 @@ * 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.system; +import java.util.List; import java.util.Map; -import javax.inject.Inject; import javax.inject.Provider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.xwiki.livedata.LiveData; import org.xwiki.livedata.LiveDataException; import org.xwiki.livedata.LiveDataQuery; import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.notifications.NotificationException; +import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.internal.FilterPreferencesModelBridge; +import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; +import org.xwiki.notifications.filters.internal.ToggleableNotificationFilterActivation; +import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; import org.xwiki.test.junit5.mockito.ComponentTest; @@ -43,7 +49,11 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.test.reference.ReferenceComponentList; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -69,6 +79,9 @@ class NotificationSystemFiltersLiveDataEntryStoreTest @MockComponent private ContextualAuthorizationManager contextualAuthorizationManager; + @MockComponent + private NotificationFilterLiveDataTranslationHelper translationHelper; + @MockComponent private Provider contextProvider; @@ -93,15 +106,400 @@ void getMissingTarget() } @Test - void getBadAuthorization() + void getBadAuthorization() throws LiveDataException + { + LiveDataQuery query = mock(LiveDataQuery.class); + LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); + when(query.getSource()).thenReturn(source); + when(source.getParameters()).thenReturn(Map.of( + "target", "wiki", + "wiki", "foo" + )); + DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "Foo"); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + when(context.getUserReference()).thenReturn(userDoc); + LiveDataException liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(query)); + assertEquals("You don't have rights to access those information.", liveDataException.getMessage()); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(true); + LiveData emptyLiveData = new LiveData(); + assertEquals(emptyLiveData, this.entryStore.get(query)); + + when(source.getParameters()).thenReturn(Map.of( + "target", "user", + "user", "xwiki:XWiki.Bar" + )); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(query)); + assertEquals("You don't have rights to access those information.", liveDataException.getMessage()); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(true); + assertEquals(emptyLiveData, this.entryStore.get(query)); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + when(source.getParameters()).thenReturn(Map.of( + "target", "user", + "user", "xwiki:XWiki.Foo" + )); + assertEquals(emptyLiveData, this.entryStore.get(query)); + } + + @Test + void getFromWiki() throws NotificationException, LiveDataException + { + LiveDataQuery query = mock(LiveDataQuery.class); + LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); + when(query.getSource()).thenReturn(source); + when(source.getParameters()).thenReturn(Map.of( + "target", "wiki", + "wiki", "foo" + )); + DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "Foo"); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(true); + when(context.getUserReference()).thenReturn(userDoc); + + String filter1Name = "filter1"; + String filter2Name = "filter2"; + String filter3Name = "filter3"; + String filter4Name = "filter4"; + String filter5Name = "filter5"; + String filter6Name = "filter6"; + String filter7Name = "filter7"; + + NotificationFilter filter1 = mock(NotificationFilter.class, filter1Name); + ToggleableNotificationFilter filter2 = mock(ToggleableNotificationFilter.class, filter2Name); + NotificationFilter filter3 = mock(NotificationFilter.class, filter3Name); + ToggleableNotificationFilter filter4 = mock(ToggleableNotificationFilter.class, filter4Name); + ToggleableNotificationFilter filter5 = mock(ToggleableNotificationFilter.class, filter5Name); + ToggleableNotificationFilter filter6 = mock(ToggleableNotificationFilter.class, filter6Name); + ToggleableNotificationFilter filter7 = mock(ToggleableNotificationFilter.class, filter7Name); + + when(filter1.getName()).thenReturn(filter1Name); + when(filter2.getName()).thenReturn(filter2Name); + when(filter3.getName()).thenReturn(filter3Name); + when(filter4.getName()).thenReturn(filter4Name); + when(filter5.getName()).thenReturn(filter5Name); + when(filter6.getName()).thenReturn(filter6Name); + when(filter7.getName()).thenReturn(filter7Name); + + WikiReference wikiReference = new WikiReference("foo"); + DocumentReference prefReference = new DocumentReference("foo", List.of("XWiki", "Notifications", "Code"), + "NotificationAdministration"); + when(this.notificationFilterManager.getAllFilters(wikiReference)).thenReturn(List.of( + // They are shuffled to test ordering + filter3, + filter4, + filter6, + filter2, + filter5, + filter1, + filter7 + )); + + when(filter2.getFormats()).thenReturn(List.of(NotificationFormat.ALERT, NotificationFormat.EMAIL)); + when(filter4.getFormats()).thenReturn(List.of()); + when(filter5.getFormats()).thenReturn(List.of(NotificationFormat.ALERT)); + when(filter6.getFormats()).thenReturn(List.of(NotificationFormat.EMAIL)); + when(filter7.getFormats()).thenReturn(List.of(NotificationFormat.EMAIL, NotificationFormat.ALERT)); + + // There's explicitely no activation data for filter2 and filter6 + ToggleableNotificationFilterActivation activationFilter4 = mock(ToggleableNotificationFilterActivation.class, + filter4Name); + ToggleableNotificationFilterActivation activationFilter5 = mock(ToggleableNotificationFilterActivation.class, + filter5Name); + ToggleableNotificationFilterActivation activationFilter7 = mock(ToggleableNotificationFilterActivation.class, + filter7Name); + when(this.filterPreferencesModelBridge.getToggleableFilterActivations(prefReference)).thenReturn(Map.of( + filter4Name, activationFilter4, + filter5Name, activationFilter5, + filter7Name, activationFilter7 + )); + + when(filter2.isEnabledByDefault()).thenReturn(true); + when(filter6.isEnabledByDefault()).thenReturn(false); + + when(filter4.isEnabledByDefault()).thenReturn(true); + when(filter5.isEnabledByDefault()).thenReturn(false); + when(filter7.isEnabledByDefault()).thenReturn(false); + + when(activationFilter4.isEnabled()).thenReturn(false); + when(activationFilter5.isEnabled()).thenReturn(true); + when(activationFilter7.isEnabled()).thenReturn(false); + + when(activationFilter4.getObjectNumber()).thenReturn(-1); + when(activationFilter5.getObjectNumber()).thenReturn(2); + when(activationFilter7.getObjectNumber()).thenReturn(14); + + // Get all info to start + when(query.getOffset()).thenReturn(0L); + when(query.getLimit()).thenReturn(10); + + when(this.translationHelper.getTranslationWithPrefix(eq("notifications.filters.name."), anyString())) + .thenAnswer(invocationOnMock -> "Name:" + invocationOnMock.getArgument(1)); + when(this.translationHelper.getTranslationWithPrefix(eq("notifications.filters.description."), anyString())) + .thenAnswer(invocationOnMock -> "Description " + invocationOnMock.getArgument(1)); + when(this.translationHelper.getFormatTranslation(any())) + .thenAnswer(invocationOnMock -> "Format " + invocationOnMock.getArgument(0)); + + Map dataFilter2 = Map.of( + "name", "Name:" + filter2Name, + "filterDescription", "Description " + filter2Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT", "Format EMAIL") + ), + "isEnabled_data", Map.of( + "objectNumber", "", + "filterName", filter2Name + ), + "isEnabled_checked", true + ); + + Map dataFilter4 = Map.of( + "name", "Name:" + filter4Name, + "filterDescription", "Description " + filter4Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of() + ), + "isEnabled_data", Map.of( + "objectNumber", "", + "filterName", filter4Name + ), + "isEnabled_checked", false + ); + + Map dataFilter5 = Map.of( + "name", "Name:" + filter5Name, + "filterDescription", "Description " + filter5Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT") + ), + "isEnabled_data", Map.of( + "objectNumber", "2", + "filterName", filter5Name + ), + "isEnabled_checked", true + ); + + Map dataFilter6 = Map.of( + "name", "Name:" + filter6Name, + "filterDescription", "Description " + filter6Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format EMAIL") + ), + "isEnabled_data", Map.of( + "objectNumber", "", + "filterName", filter6Name + ), + "isEnabled_checked", false + ); + + Map dataFilter7 = Map.of( + "name", "Name:" + filter7Name, + "filterDescription", "Description " + filter7Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT", "Format EMAIL") + ), + "isEnabled_data", Map.of( + "objectNumber", "14", + "filterName", filter7Name + ), + "isEnabled_checked", false + ); + + LiveData liveData = new LiveData(); + liveData.setCount(5L); + liveData.getEntries().addAll(List.of(dataFilter2, dataFilter4, dataFilter5, dataFilter6, dataFilter7)); + assertEquals(liveData, this.entryStore.get(query)); + + when(query.getOffset()).thenReturn(2L); + when(query.getLimit()).thenReturn(2); + + liveData = new LiveData(); + liveData.setCount(5L); + liveData.getEntries().addAll(List.of(dataFilter5, dataFilter6)); + assertEquals(liveData, this.entryStore.get(query)); + } + + @Test + void getFromUser() throws NotificationException, LiveDataException { LiveDataQuery query = mock(LiveDataQuery.class); LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); when(query.getSource()).thenReturn(source); - String target = "wiki"; - String wiki = "Wiki foo"; + when(source.getParameters()).thenReturn(Map.of( + "target", "user", + "user", "xwiki:XWiki.Foo" + )); DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "Foo"); when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); when(context.getUserReference()).thenReturn(userDoc); + + String filter1Name = "filter1"; + String filter2Name = "filter2"; + String filter3Name = "filter3"; + String filter4Name = "filter4"; + String filter5Name = "filter5"; + String filter6Name = "filter6"; + String filter7Name = "filter7"; + + NotificationFilter filter1 = mock(NotificationFilter.class, filter1Name); + ToggleableNotificationFilter filter2 = mock(ToggleableNotificationFilter.class, filter2Name); + NotificationFilter filter3 = mock(NotificationFilter.class, filter3Name); + ToggleableNotificationFilter filter4 = mock(ToggleableNotificationFilter.class, filter4Name); + ToggleableNotificationFilter filter5 = mock(ToggleableNotificationFilter.class, filter5Name); + ToggleableNotificationFilter filter6 = mock(ToggleableNotificationFilter.class, filter6Name); + ToggleableNotificationFilter filter7 = mock(ToggleableNotificationFilter.class, filter7Name); + + when(filter1.getName()).thenReturn(filter1Name); + when(filter2.getName()).thenReturn(filter2Name); + when(filter3.getName()).thenReturn(filter3Name); + when(filter4.getName()).thenReturn(filter4Name); + when(filter5.getName()).thenReturn(filter5Name); + when(filter6.getName()).thenReturn(filter6Name); + when(filter7.getName()).thenReturn(filter7Name); + + when(this.notificationFilterManager.getAllFilters(userDoc, false)).thenReturn(List.of( + // They are shuffled to test ordering + filter3, + filter4, + filter6, + filter2, + filter5, + filter1, + filter7 + )); + + when(filter2.getFormats()).thenReturn(List.of(NotificationFormat.ALERT, NotificationFormat.EMAIL)); + when(filter4.getFormats()).thenReturn(List.of()); + when(filter5.getFormats()).thenReturn(List.of(NotificationFormat.ALERT)); + when(filter6.getFormats()).thenReturn(List.of(NotificationFormat.EMAIL)); + when(filter7.getFormats()).thenReturn(List.of(NotificationFormat.EMAIL, NotificationFormat.ALERT)); + + // There's explicitely no activation data for filter2 and filter6 + ToggleableNotificationFilterActivation activationFilter4 = mock(ToggleableNotificationFilterActivation.class, + filter4Name); + ToggleableNotificationFilterActivation activationFilter5 = mock(ToggleableNotificationFilterActivation.class, + filter5Name); + ToggleableNotificationFilterActivation activationFilter7 = mock(ToggleableNotificationFilterActivation.class, + filter7Name); + when(this.filterPreferencesModelBridge.getToggleableFilterActivations(userDoc)).thenReturn(Map.of( + filter4Name, activationFilter4, + filter5Name, activationFilter5, + filter7Name, activationFilter7 + )); + + when(filter2.isEnabledByDefault()).thenReturn(true); + when(filter6.isEnabledByDefault()).thenReturn(false); + + when(filter4.isEnabledByDefault()).thenReturn(true); + when(filter5.isEnabledByDefault()).thenReturn(false); + when(filter7.isEnabledByDefault()).thenReturn(false); + + when(activationFilter4.isEnabled()).thenReturn(false); + when(activationFilter5.isEnabled()).thenReturn(true); + when(activationFilter7.isEnabled()).thenReturn(false); + + when(activationFilter4.getObjectNumber()).thenReturn(-1); + when(activationFilter5.getObjectNumber()).thenReturn(2); + when(activationFilter7.getObjectNumber()).thenReturn(14); + + // Get all info to start + when(query.getOffset()).thenReturn(0L); + when(query.getLimit()).thenReturn(10); + + when(this.translationHelper.getTranslationWithPrefix(eq("notifications.filters.name."), anyString())) + .thenAnswer(invocationOnMock -> "Name:" + invocationOnMock.getArgument(1)); + when(this.translationHelper.getTranslationWithPrefix(eq("notifications.filters.description."), anyString())) + .thenAnswer(invocationOnMock -> "Description " + invocationOnMock.getArgument(1)); + when(this.translationHelper.getFormatTranslation(any())) + .thenAnswer(invocationOnMock -> "Format " + invocationOnMock.getArgument(0)); + + Map dataFilter2 = Map.of( + "name", "Name:" + filter2Name, + "filterDescription", "Description " + filter2Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT", "Format EMAIL") + ), + "isEnabled_data", Map.of( + "objectNumber", "", + "filterName", filter2Name + ), + "isEnabled_checked", true + ); + + Map dataFilter4 = Map.of( + "name", "Name:" + filter4Name, + "filterDescription", "Description " + filter4Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of() + ), + "isEnabled_data", Map.of( + "objectNumber", "", + "filterName", filter4Name + ), + "isEnabled_checked", false + ); + + Map dataFilter5 = Map.of( + "name", "Name:" + filter5Name, + "filterDescription", "Description " + filter5Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT") + ), + "isEnabled_data", Map.of( + "objectNumber", "2", + "filterName", filter5Name + ), + "isEnabled_checked", true + ); + + Map dataFilter6 = Map.of( + "name", "Name:" + filter6Name, + "filterDescription", "Description " + filter6Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format EMAIL") + ), + "isEnabled_data", Map.of( + "objectNumber", "", + "filterName", filter6Name + ), + "isEnabled_checked", false + ); + + Map dataFilter7 = Map.of( + "name", "Name:" + filter7Name, + "filterDescription", "Description " + filter7Name, + "notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT", "Format EMAIL") + ), + "isEnabled_data", Map.of( + "objectNumber", "14", + "filterName", filter7Name + ), + "isEnabled_checked", false + ); + + LiveData liveData = new LiveData(); + liveData.setCount(5L); + liveData.getEntries().addAll(List.of(dataFilter2, dataFilter4, dataFilter5, dataFilter6, dataFilter7)); + assertEquals(liveData, this.entryStore.get(query)); + + when(query.getOffset()).thenReturn(2L); + when(query.getLimit()).thenReturn(2); + + liveData = new LiveData(); + liveData.setCount(5L); + liveData.getEntries().addAll(List.of(dataFilter5, dataFilter6)); + assertEquals(liveData, this.entryStore.get(query)); } } \ No newline at end of file From c50b86a9d17f089e36f852eb767dec7464d4bd81 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Mon, 18 Mar 2024 15:26:20 +0100 Subject: [PATCH 20/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Provide new unit test * Add equals/toString methods in DefaultQueryParameter * Provide new unit test in module query-manager to fix coverage --- .../NotificationCustomFiltersQueryHelper.java | 4 +- ...ificationCustomFiltersQueryHelperTest.java | 426 ++++++++++++++++++ .../query/internal/DefaultQueryParameter.java | 25 + .../xwiki/query/internal/ParameterPart.java | 25 + .../org/xwiki/query/internal/ScriptQuery.java | 27 ++ .../script/QueryManagerScriptServiceTest.java | 103 +++++ 6 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelperTest.java create mode 100644 xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/test/java/org/xwiki/query/script/QueryManagerScriptServiceTest.java diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java index 306f8ed39561..685c31ce5906 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -128,7 +128,7 @@ private void handleScopeFilter(LiveDataQuery.Filter queryFilter, List qu NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( String.valueOf(constraint.getValue())); - queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); + queryWhereClauses.add(String.format("length(nfp.%s) > 0", scope.getFieldName())); } } @@ -168,7 +168,7 @@ private void handleEventTypeFilter(LiveDataQuery.Filter queryFilter, List expectedList = List.of( + mock(NotificationFilterPreference.class, "filterPref1"), + mock(NotificationFilterPreference.class, "filterPref2"), + mock(NotificationFilterPreference.class, "filterPref3") + ); + when(query.execute()).thenReturn(expectedList); + assertEquals(expectedList, this.queryHelper.getFilterPreferences(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).setWiki(wikiName); + verify(query).setOffset(offset.intValue()); + verify(query).setLimit(limit); + } + + @Test + void countFilterPreferencesNoFilterNoSort() throws QueryException, LiveDataException + { + String owner = "xwiki:XWiki.Foo"; + String wikiName = "foo"; + WikiReference wikiReference = new WikiReference(wikiName); + String queryString = "select count(nfp.id) from DefaultNotificationFilterPreference nfp where owner = :owner"; + Query query = mock(Query.class); + when(this.queryManager.createQuery(queryString, Query.HQL)).thenReturn(query); + when(query.bindValue("owner", owner)).thenReturn(query); + when(query.setWiki(wikiName)).thenReturn(query); + Long offset = 3L; + int limit = 12; + + LiveDataQuery ldQuery = mock(LiveDataQuery.class); + when(ldQuery.getOffset()).thenReturn(offset); + when(ldQuery.getLimit()).thenReturn(limit); + + when(query.execute()).thenReturn(List.of(3L)); + assertEquals(3L, this.queryHelper.countTotalFilters(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).setWiki(wikiName); + verify(query, never()).setOffset(anyInt()); + verify(query, never()).setLimit(anyInt()); + } + + @Test + void getFilterPreferencesNoFilterSort() throws QueryException, LiveDataException + { + String owner = "xwiki:XWiki.Foo"; + String wikiName = "foo"; + WikiReference wikiReference = new WikiReference(wikiName); + Long offset = 3L; + int limit = 12; + + LiveDataQuery ldQuery = mock(LiveDataQuery.class); + when(ldQuery.getOffset()).thenReturn(offset); + when(ldQuery.getLimit()).thenReturn(limit); + + LiveDataQuery.SortEntry sortEntry1 = mock(LiveDataQuery.SortEntry.class, "sortEntry1"); + when(sortEntry1.isDescending()).thenReturn(false); + when(sortEntry1.getProperty()).thenReturn("scope"); + + LiveDataQuery.SortEntry sortEntry2 = mock(LiveDataQuery.SortEntry.class, "sortEntry2"); + when(sortEntry2.isDescending()).thenReturn(true); + when(sortEntry2.getProperty()).thenReturn("isEnabled"); + + LiveDataQuery.SortEntry sortEntry3 = mock(LiveDataQuery.SortEntry.class, "sortEntry3"); + when(sortEntry3.isDescending()).thenReturn(true); + when(sortEntry3.getProperty()).thenReturn("notificationFormats"); + + when(ldQuery.getSort()).thenReturn(List.of(sortEntry1, sortEntry2, sortEntry3)); + + String queryString = "select nfp from DefaultNotificationFilterPreference nfp where owner = :owner " + + "order by nfp.pageOnly asc, nfp.page asc, nfp.wiki asc, nfp.user asc, nfp.enabled desc, " + + "nfp.emailEnabled asc, nfp.alertEnabled desc"; + Query query = mock(Query.class); + when(this.queryManager.createQuery(queryString, Query.HQL)).thenReturn(query); + when(query.bindValue("owner", owner)).thenReturn(query); + when(query.setWiki(wikiName)).thenReturn(query); + when(query.setOffset(offset.intValue())).thenReturn(query); + when(query.setLimit(limit)).thenReturn(query); + + List expectedList = List.of( + mock(NotificationFilterPreference.class, "filterPref1"), + mock(NotificationFilterPreference.class, "filterPref2"), + mock(NotificationFilterPreference.class, "filterPref3") + ); + when(query.execute()).thenReturn(expectedList); + assertEquals(expectedList, this.queryHelper.getFilterPreferences(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).setWiki(wikiName); + verify(query).setOffset(offset.intValue()); + verify(query).setLimit(limit); + } + + @Test + void countFilterPreferencesNoFilterSort() throws QueryException, LiveDataException + { + String owner = "xwiki:XWiki.Foo"; + String wikiName = "foo"; + WikiReference wikiReference = new WikiReference(wikiName); + + Long offset = 3L; + int limit = 12; + + LiveDataQuery ldQuery = mock(LiveDataQuery.class); + when(ldQuery.getOffset()).thenReturn(offset); + when(ldQuery.getLimit()).thenReturn(limit); + + LiveDataQuery.SortEntry sortEntry1 = mock(LiveDataQuery.SortEntry.class, "sortEntry1"); + when(sortEntry1.isDescending()).thenReturn(false); + when(sortEntry1.getProperty()).thenReturn("scope"); + + LiveDataQuery.SortEntry sortEntry2 = mock(LiveDataQuery.SortEntry.class, "sortEntry2"); + when(sortEntry2.isDescending()).thenReturn(true); + when(sortEntry2.getProperty()).thenReturn("isEnabled"); + + LiveDataQuery.SortEntry sortEntry3 = mock(LiveDataQuery.SortEntry.class, "sortEntry3"); + when(sortEntry3.isDescending()).thenReturn(true); + when(sortEntry3.getProperty()).thenReturn("notificationFormats"); + + when(ldQuery.getSort()).thenReturn(List.of(sortEntry1, sortEntry2, sortEntry3)); + + String queryString = "select count(nfp.id) from DefaultNotificationFilterPreference nfp where owner = :owner"; + Query query = mock(Query.class); + when(this.queryManager.createQuery(queryString, Query.HQL)).thenReturn(query); + when(query.bindValue("owner", owner)).thenReturn(query); + when(query.setWiki(wikiName)).thenReturn(query); + + when(query.execute()).thenReturn(List.of(3L)); + assertEquals(3L, this.queryHelper.countTotalFilters(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).setWiki(wikiName); + verify(query, never()).setOffset(anyInt()); + verify(query, never()).setLimit(anyInt()); + } + + @Test + void getFilterPreferencesFilterNoSort() throws QueryException, LiveDataException + { + String owner = "xwiki:XWiki.Foo"; + String wikiName = "foo"; + Long offset = 3L; + int limit = 12; + WikiReference wikiReference = new WikiReference(wikiName); + + LiveDataQuery ldQuery = mock(LiveDataQuery.class); + when(ldQuery.getOffset()).thenReturn(offset); + when(ldQuery.getLimit()).thenReturn(limit); + + LiveDataQuery.Filter filter1 = mock(LiveDataQuery.Filter.class, "filter1"); + when(filter1.getProperty()).thenReturn("location"); + when(filter1.isMatchAll()).thenReturn(false); + LiveDataQuery.Constraint filter1Constraint1 = mock(LiveDataQuery.Constraint.class, "filter1Constraint1"); + when(filter1Constraint1.getOperator()).thenReturn("startsWith"); + when(filter1Constraint1.getValue()).thenReturn("foo"); + LiveDataQuery.Constraint filter1Constraint2 = mock(LiveDataQuery.Constraint.class, "filter1Constraint2"); + when(filter1Constraint2.getOperator()).thenReturn("contains"); + when(filter1Constraint2.getValue()).thenReturn("bar"); + LiveDataQuery.Constraint filter1Constraint3 = mock(LiveDataQuery.Constraint.class, "filter1Constraint3"); + when(filter1Constraint3.getOperator()).thenReturn("equals"); + when(filter1Constraint3.getValue()).thenReturn("buz"); + when(filter1.getConstraints()).thenReturn(List.of(filter1Constraint1, filter1Constraint2, filter1Constraint3)); + + LiveDataQuery.Filter filter2 = mock(LiveDataQuery.Filter.class, "filter2"); + when(filter2.getProperty()).thenReturn("eventTypes"); + LiveDataQuery.Constraint filter2Constraint = mock(LiveDataQuery.Constraint.class, "filter2Constraint"); + when(filter2Constraint.getOperator()).thenReturn("equals"); + when(filter2Constraint.getValue()).thenReturn("__ALL_EVENTS__"); + when(filter2.getConstraints()).thenReturn(List.of(filter2Constraint)); + + LiveDataQuery.Filter filter3 = mock(LiveDataQuery.Filter.class, "filter3"); + when(filter3.getProperty()).thenReturn("isEnabled"); + LiveDataQuery.Constraint filter3Constraint = mock(LiveDataQuery.Constraint.class, "filter3Constraint"); + when(filter3Constraint.getOperator()).thenReturn("equals"); + when(filter3Constraint.getValue()).thenReturn("true"); + when(filter3.getConstraints()).thenReturn(List.of(filter3Constraint)); + + LiveDataQuery.Filter filter4 = mock(LiveDataQuery.Filter.class, "filter4"); + when(filter4.getProperty()).thenReturn("scope"); + LiveDataQuery.Constraint filter4Constraint = mock(LiveDataQuery.Constraint.class, "filter4Constraint"); + when(filter4Constraint.getOperator()).thenReturn("equals"); + when(filter4Constraint.getValue()).thenReturn("PAGE"); + when(filter4.getConstraints()).thenReturn(List.of(filter4Constraint)); + + LiveDataQuery.Filter filter5 = mock(LiveDataQuery.Filter.class, "filter5"); + when(filter5.getProperty()).thenReturn("filterType"); + LiveDataQuery.Constraint filter5Constraint = mock(LiveDataQuery.Constraint.class, "filter5Constraint"); + when(filter5Constraint.getOperator()).thenReturn("equals"); + when(filter5Constraint.getValue()).thenReturn("INCLUSIVE"); + when(filter5.getConstraints()).thenReturn(List.of(filter5Constraint)); + + when(ldQuery.getFilters()).thenReturn(List.of( + filter1, + filter2, + filter3, + filter4, + filter5 + )); + + String queryString = "select nfp from DefaultNotificationFilterPreference nfp where owner = :owner " + + "and ((nfp.pageOnly like :constraint_0 or nfp.page like :constraint_0 or nfp.wiki like :constraint_0 or " + + "nfp.user like :constraint_0) or (nfp.pageOnly like :constraint_1 or nfp.page like :constraint_1 or " + + "nfp.wiki like :constraint_1 or nfp.user like :constraint_1) or (nfp.pageOnly = :constraint_2 or " + + "nfp.page = :constraint_2 or nfp.wiki = :constraint_2 or nfp.user = :constraint_2)) and " + + "length(nfp.allEventTypes) = 0 and nfp.enabled = 1 and length(nfp.pageOnly) > 0 and " + + "nfp.filterType = :filterType"; + + Query query = mock(Query.class); + when(this.queryManager.createQuery(queryString, Query.HQL)).thenReturn(query); + when(query.bindValue("owner", owner)).thenReturn(query); + + DefaultQueryParameter queryParameter1 = new DefaultQueryParameter(null); + queryParameter1.literal("foo").anyChars(); + when(query.bindValue("constraint_0", queryParameter1)).thenReturn(query); + + DefaultQueryParameter queryParameter2 = new DefaultQueryParameter(null); + queryParameter2.anyChars().literal("bar").anyChars(); + when(query.bindValue("constraint_1", queryParameter2)).thenReturn(query); + + DefaultQueryParameter queryParameter3 = new DefaultQueryParameter(null); + queryParameter3.literal("buz"); + when(query.bindValue("constraint_2", queryParameter3)).thenReturn(query); + + when(query.bindValue("filterType", NotificationFilterType.INCLUSIVE)).thenReturn(query); + + when(query.setWiki(wikiName)).thenReturn(query); + when(query.setOffset(offset.intValue())).thenReturn(query); + when(query.setLimit(limit)).thenReturn(query); + + List expectedList = List.of( + mock(NotificationFilterPreference.class, "filterPref1"), + mock(NotificationFilterPreference.class, "filterPref2"), + mock(NotificationFilterPreference.class, "filterPref3") + ); + when(query.execute()).thenReturn(expectedList); + assertEquals(expectedList, this.queryHelper.getFilterPreferences(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).bindValue("constraint_0", queryParameter1); + verify(query).bindValue("constraint_1", queryParameter2); + verify(query).bindValue("constraint_2", queryParameter3); + verify(query).bindValue("filterType", NotificationFilterType.INCLUSIVE); + verify(query).setWiki(wikiName); + verify(query).setOffset(offset.intValue()); + verify(query).setLimit(limit); + } + + @Test + void countFilterPreferencesFilterNoSort() throws QueryException, LiveDataException + { + String owner = "xwiki:XWiki.Foo"; + String wikiName = "foo"; + Long offset = 3L; + int limit = 12; + WikiReference wikiReference = new WikiReference(wikiName); + + LiveDataQuery ldQuery = mock(LiveDataQuery.class); + when(ldQuery.getOffset()).thenReturn(offset); + when(ldQuery.getLimit()).thenReturn(limit); + + LiveDataQuery.Filter filter1 = mock(LiveDataQuery.Filter.class, "filter1"); + when(filter1.getProperty()).thenReturn("location"); + when(filter1.isMatchAll()).thenReturn(false); + LiveDataQuery.Constraint filter1Constraint1 = mock(LiveDataQuery.Constraint.class, "filter1Constraint1"); + when(filter1Constraint1.getOperator()).thenReturn("startsWith"); + when(filter1Constraint1.getValue()).thenReturn("foo"); + LiveDataQuery.Constraint filter1Constraint2 = mock(LiveDataQuery.Constraint.class, "filter1Constraint2"); + when(filter1Constraint2.getOperator()).thenReturn("contains"); + when(filter1Constraint2.getValue()).thenReturn("bar"); + LiveDataQuery.Constraint filter1Constraint3 = mock(LiveDataQuery.Constraint.class, "filter1Constraint3"); + when(filter1Constraint3.getOperator()).thenReturn("equals"); + when(filter1Constraint3.getValue()).thenReturn("buz"); + when(filter1.getConstraints()).thenReturn(List.of(filter1Constraint1, filter1Constraint2, filter1Constraint3)); + + LiveDataQuery.Filter filter2 = mock(LiveDataQuery.Filter.class, "filter2"); + when(filter2.getProperty()).thenReturn("eventTypes"); + LiveDataQuery.Constraint filter2Constraint = mock(LiveDataQuery.Constraint.class, "filter2Constraint"); + when(filter2Constraint.getOperator()).thenReturn("equals"); + when(filter2Constraint.getValue()).thenReturn("__ALL_EVENTS__"); + when(filter2.getConstraints()).thenReturn(List.of(filter2Constraint)); + + LiveDataQuery.Filter filter3 = mock(LiveDataQuery.Filter.class, "filter3"); + when(filter3.getProperty()).thenReturn("isEnabled"); + LiveDataQuery.Constraint filter3Constraint = mock(LiveDataQuery.Constraint.class, "filter3Constraint"); + when(filter3Constraint.getOperator()).thenReturn("equals"); + when(filter3Constraint.getValue()).thenReturn("true"); + when(filter3.getConstraints()).thenReturn(List.of(filter3Constraint)); + + LiveDataQuery.Filter filter4 = mock(LiveDataQuery.Filter.class, "filter4"); + when(filter4.getProperty()).thenReturn("scope"); + LiveDataQuery.Constraint filter4Constraint = mock(LiveDataQuery.Constraint.class, "filter4Constraint"); + when(filter4Constraint.getOperator()).thenReturn("equals"); + when(filter4Constraint.getValue()).thenReturn("PAGE"); + when(filter4.getConstraints()).thenReturn(List.of(filter4Constraint)); + + LiveDataQuery.Filter filter5 = mock(LiveDataQuery.Filter.class, "filter5"); + when(filter5.getProperty()).thenReturn("filterType"); + LiveDataQuery.Constraint filter5Constraint = mock(LiveDataQuery.Constraint.class, "filter5Constraint"); + when(filter5Constraint.getOperator()).thenReturn("equals"); + when(filter5Constraint.getValue()).thenReturn("INCLUSIVE"); + when(filter5.getConstraints()).thenReturn(List.of(filter5Constraint)); + + when(ldQuery.getFilters()).thenReturn(List.of( + filter1, + filter2, + filter3, + filter4, + filter5 + )); + + String queryString = "select count(nfp.id) from DefaultNotificationFilterPreference nfp where owner = :owner " + + "and ((nfp.pageOnly like :constraint_0 or nfp.page like :constraint_0 or nfp.wiki like :constraint_0 or " + + "nfp.user like :constraint_0) or (nfp.pageOnly like :constraint_1 or nfp.page like :constraint_1 or " + + "nfp.wiki like :constraint_1 or nfp.user like :constraint_1) or (nfp.pageOnly = :constraint_2 or " + + "nfp.page = :constraint_2 or nfp.wiki = :constraint_2 or nfp.user = :constraint_2)) and " + + "length(nfp.allEventTypes) = 0 and nfp.enabled = 1 and length(nfp.pageOnly) > 0 and " + + "nfp.filterType = :filterType"; + + Query query = mock(Query.class); + when(this.queryManager.createQuery(queryString, Query.HQL)).thenReturn(query); + when(query.bindValue("owner", owner)).thenReturn(query); + + DefaultQueryParameter queryParameter1 = new DefaultQueryParameter(null); + queryParameter1.literal("foo").anyChars(); + when(query.bindValue("constraint_0", queryParameter1)).thenReturn(query); + + DefaultQueryParameter queryParameter2 = new DefaultQueryParameter(null); + queryParameter2.anyChars().literal("bar").anyChars(); + when(query.bindValue("constraint_1", queryParameter2)).thenReturn(query); + + DefaultQueryParameter queryParameter3 = new DefaultQueryParameter(null); + queryParameter3.literal("buz"); + when(query.bindValue("constraint_2", queryParameter3)).thenReturn(query); + + when(query.bindValue("filterType", NotificationFilterType.INCLUSIVE)).thenReturn(query); + + when(query.setWiki(wikiName)).thenReturn(query); + + when(query.execute()).thenReturn(List.of(3L)); + assertEquals(3L, this.queryHelper.countTotalFilters(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).bindValue("constraint_0", queryParameter1); + verify(query).bindValue("constraint_1", queryParameter2); + verify(query).bindValue("constraint_2", queryParameter3); + verify(query).bindValue("filterType", NotificationFilterType.INCLUSIVE); + verify(query).setWiki(wikiName); + verify(query, never()).setOffset(anyInt()); + verify(query, never()).setLimit(anyInt()); + } +} \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/DefaultQueryParameter.java b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/DefaultQueryParameter.java index 59eeda298385..e0ef0a067418 100644 --- a/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/DefaultQueryParameter.java +++ b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/DefaultQueryParameter.java @@ -22,6 +22,8 @@ import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.xwiki.query.Query; import org.xwiki.query.QueryParameter; @@ -96,4 +98,27 @@ public Query query() { return this.query; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultQueryParameter that = (DefaultQueryParameter) o; + + return new EqualsBuilder().append(parts, that.parts).append(query, that.query) + .isEquals(); + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(17, 95).append(parts).append(query).toHashCode(); + } } diff --git a/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ParameterPart.java b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ParameterPart.java index 8b7ff696e607..6f3681372396 100644 --- a/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ParameterPart.java +++ b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ParameterPart.java @@ -19,6 +19,9 @@ */ package org.xwiki.query.internal; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + /** * Represents a part of a HQL parameter. There can be several parts since we separate literals (ie characters that * will be escaped) vs special characters having a SQL meaning and that won't be escaped (ie {@code _} and {@code %}). @@ -46,4 +49,26 @@ public String getValue() { return this.value; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + ParameterPart that = (ParameterPart) o; + + return new EqualsBuilder().append(value, that.value).isEquals(); + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(63, 37).append(value).toHashCode(); + } } diff --git a/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ScriptQuery.java b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ScriptQuery.java index 1193b6d8fc56..fda1952f179a 100644 --- a/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ScriptQuery.java +++ b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/main/java/org/xwiki/query/internal/ScriptQuery.java @@ -24,6 +24,8 @@ import java.util.concurrent.Callable; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -339,4 +341,29 @@ public SecureQuery checkCurrentUser(boolean checkCurrentUser) return this; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + ScriptQuery that = (ScriptQuery) o; + + return new EqualsBuilder().append(switchAuthor, that.switchAuthor) + .append(componentManager, that.componentManager).append(query, that.query) + .append(authorReference, that.authorReference).append(sourceReference, that.sourceReference).isEquals(); + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(17, 37).append(componentManager).append(query).append(switchAuthor) + .append(authorReference).append(sourceReference).toHashCode(); + } } diff --git a/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/test/java/org/xwiki/query/script/QueryManagerScriptServiceTest.java b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/test/java/org/xwiki/query/script/QueryManagerScriptServiceTest.java new file mode 100644 index 000000000000..9bff6a94661d --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/test/java/org/xwiki/query/script/QueryManagerScriptServiceTest.java @@ -0,0 +1,103 @@ +/* + * 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.query.script; + +import javax.inject.Named; + +import org.junit.jupiter.api.Test; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.query.Query; +import org.xwiki.query.QueryException; +import org.xwiki.query.QueryManager; +import org.xwiki.query.SecureQuery; +import org.xwiki.query.internal.DefaultQueryParameter; +import org.xwiki.query.internal.ScriptQuery; +import org.xwiki.test.junit5.mockito.ComponentTest; +import org.xwiki.test.junit5.mockito.InjectMockComponents; +import org.xwiki.test.junit5.mockito.MockComponent; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link QueryManagerScriptService}. + * + * @version $Id$ + * @since 16.3.0RC1 + */ +@ComponentTest +class QueryManagerScriptServiceTest +{ + @InjectMockComponents + private QueryManagerScriptService queryManagerScriptService; + + @MockComponent + @Named("secure") + private QueryManager secureQueryManager; + + @MockComponent + private ComponentManager componentManager; + + @Test + void xwql() throws QueryException + { + String statement = "some statement"; + SecureQuery secureQuery = mock(SecureQuery.class); + when(this.secureQueryManager.createQuery(statement, Query.XWQL)).thenReturn(secureQuery); + ScriptQuery scriptQuery = new ScriptQuery(secureQuery, this.componentManager); + assertEquals(scriptQuery, this.queryManagerScriptService.xwql(statement)); + verify(secureQuery).checkCurrentUser(false); + verify(secureQuery).checkCurrentAuthor(true); + } + + @Test + void hql() throws QueryException + { + String statement = "some statement"; + SecureQuery secureQuery = mock(SecureQuery.class); + when(this.secureQueryManager.createQuery(statement, Query.HQL)).thenReturn(secureQuery); + ScriptQuery scriptQuery = new ScriptQuery(secureQuery, this.componentManager); + assertEquals(scriptQuery, this.queryManagerScriptService.hql(statement)); + verify(secureQuery).checkCurrentUser(false); + verify(secureQuery).checkCurrentAuthor(true); + } + + @Test + void createQuery() throws QueryException + { + String statement = "some statement"; + String language = "some language"; + SecureQuery secureQuery = mock(SecureQuery.class); + when(this.secureQueryManager.createQuery(statement, language)).thenReturn(secureQuery); + ScriptQuery scriptQuery = new ScriptQuery(secureQuery, this.componentManager); + assertEquals(scriptQuery, this.queryManagerScriptService.createQuery(statement, language)); + verify(secureQuery).checkCurrentUser(true); + verify(secureQuery).checkCurrentAuthor(true); + } + + @Test + void parameter() + { + assertEquals(new DefaultQueryParameter(null), this.queryManagerScriptService.parameter()); + } +} \ No newline at end of file From 5aa87cbed3102a8dff9de588c5dfeb4b7b2ed694 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Mon, 18 Mar 2024 15:29:16 +0100 Subject: [PATCH 21/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Improve a bit the unit test --- ...ificationCustomFiltersQueryHelperTest.java | 247 +++++++++++++++++- 1 file changed, 244 insertions(+), 3 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelperTest.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelperTest.java index 426942a8a991..3979d50e6812 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelperTest.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelperTest.java @@ -36,9 +36,7 @@ import org.xwiki.test.junit5.mockito.InjectMockComponents; import org.xwiki.test.junit5.mockito.MockComponent; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -423,4 +421,247 @@ void countFilterPreferencesFilterNoSort() throws QueryException, LiveDataExcepti verify(query, never()).setOffset(anyInt()); verify(query, never()).setLimit(anyInt()); } + + @Test + void getFilterPreferencesFilterSort() throws QueryException, LiveDataException + { + String owner = "xwiki:XWiki.Foo"; + String wikiName = "foo"; + Long offset = 3L; + int limit = 12; + WikiReference wikiReference = new WikiReference(wikiName); + + LiveDataQuery ldQuery = mock(LiveDataQuery.class); + when(ldQuery.getOffset()).thenReturn(offset); + when(ldQuery.getLimit()).thenReturn(limit); + + LiveDataQuery.Filter filter1 = mock(LiveDataQuery.Filter.class, "filter1"); + when(filter1.getProperty()).thenReturn("location"); + when(filter1.isMatchAll()).thenReturn(false); + LiveDataQuery.Constraint filter1Constraint1 = mock(LiveDataQuery.Constraint.class, "filter1Constraint1"); + when(filter1Constraint1.getOperator()).thenReturn("startsWith"); + when(filter1Constraint1.getValue()).thenReturn("foo"); + LiveDataQuery.Constraint filter1Constraint2 = mock(LiveDataQuery.Constraint.class, "filter1Constraint2"); + when(filter1Constraint2.getOperator()).thenReturn("contains"); + when(filter1Constraint2.getValue()).thenReturn("bar"); + LiveDataQuery.Constraint filter1Constraint3 = mock(LiveDataQuery.Constraint.class, "filter1Constraint3"); + when(filter1Constraint3.getOperator()).thenReturn("equals"); + when(filter1Constraint3.getValue()).thenReturn("buz"); + when(filter1.getConstraints()).thenReturn(List.of(filter1Constraint1, filter1Constraint2, filter1Constraint3)); + + LiveDataQuery.Filter filter2 = mock(LiveDataQuery.Filter.class, "filter2"); + when(filter2.getProperty()).thenReturn("eventTypes"); + LiveDataQuery.Constraint filter2Constraint = mock(LiveDataQuery.Constraint.class, "filter2Constraint"); + when(filter2Constraint.getOperator()).thenReturn("equals"); + when(filter2Constraint.getValue()).thenReturn("__ALL_EVENTS__"); + when(filter2.getConstraints()).thenReturn(List.of(filter2Constraint)); + + LiveDataQuery.Filter filter3 = mock(LiveDataQuery.Filter.class, "filter3"); + when(filter3.getProperty()).thenReturn("isEnabled"); + LiveDataQuery.Constraint filter3Constraint = mock(LiveDataQuery.Constraint.class, "filter3Constraint"); + when(filter3Constraint.getOperator()).thenReturn("equals"); + when(filter3Constraint.getValue()).thenReturn("true"); + when(filter3.getConstraints()).thenReturn(List.of(filter3Constraint)); + + LiveDataQuery.Filter filter4 = mock(LiveDataQuery.Filter.class, "filter4"); + when(filter4.getProperty()).thenReturn("scope"); + LiveDataQuery.Constraint filter4Constraint = mock(LiveDataQuery.Constraint.class, "filter4Constraint"); + when(filter4Constraint.getOperator()).thenReturn("equals"); + when(filter4Constraint.getValue()).thenReturn("PAGE"); + when(filter4.getConstraints()).thenReturn(List.of(filter4Constraint)); + + LiveDataQuery.Filter filter5 = mock(LiveDataQuery.Filter.class, "filter5"); + when(filter5.getProperty()).thenReturn("filterType"); + LiveDataQuery.Constraint filter5Constraint = mock(LiveDataQuery.Constraint.class, "filter5Constraint"); + when(filter5Constraint.getOperator()).thenReturn("equals"); + when(filter5Constraint.getValue()).thenReturn("INCLUSIVE"); + when(filter5.getConstraints()).thenReturn(List.of(filter5Constraint)); + + when(ldQuery.getFilters()).thenReturn(List.of( + filter1, + filter2, + filter3, + filter4, + filter5 + )); + + LiveDataQuery.SortEntry sortEntry1 = mock(LiveDataQuery.SortEntry.class, "sortEntry1"); + when(sortEntry1.isDescending()).thenReturn(false); + when(sortEntry1.getProperty()).thenReturn("scope"); + + LiveDataQuery.SortEntry sortEntry2 = mock(LiveDataQuery.SortEntry.class, "sortEntry2"); + when(sortEntry2.isDescending()).thenReturn(true); + when(sortEntry2.getProperty()).thenReturn("isEnabled"); + + LiveDataQuery.SortEntry sortEntry3 = mock(LiveDataQuery.SortEntry.class, "sortEntry3"); + when(sortEntry3.isDescending()).thenReturn(true); + when(sortEntry3.getProperty()).thenReturn("notificationFormats"); + + when(ldQuery.getSort()).thenReturn(List.of(sortEntry1, sortEntry2, sortEntry3)); + + String queryString = "select nfp from DefaultNotificationFilterPreference nfp where owner = :owner " + + "and ((nfp.pageOnly like :constraint_0 or nfp.page like :constraint_0 or nfp.wiki like :constraint_0 or " + + "nfp.user like :constraint_0) or (nfp.pageOnly like :constraint_1 or nfp.page like :constraint_1 or " + + "nfp.wiki like :constraint_1 or nfp.user like :constraint_1) or (nfp.pageOnly = :constraint_2 or " + + "nfp.page = :constraint_2 or nfp.wiki = :constraint_2 or nfp.user = :constraint_2)) and " + + "length(nfp.allEventTypes) = 0 and nfp.enabled = 1 and length(nfp.pageOnly) > 0 and " + + "nfp.filterType = :filterType " + + "order by nfp.pageOnly asc, nfp.page asc, nfp.wiki asc, nfp.user asc, nfp.enabled desc, " + + "nfp.emailEnabled asc, nfp.alertEnabled desc"; + + Query query = mock(Query.class); + when(this.queryManager.createQuery(queryString, Query.HQL)).thenReturn(query); + when(query.bindValue("owner", owner)).thenReturn(query); + + DefaultQueryParameter queryParameter1 = new DefaultQueryParameter(null); + queryParameter1.literal("foo").anyChars(); + when(query.bindValue("constraint_0", queryParameter1)).thenReturn(query); + + DefaultQueryParameter queryParameter2 = new DefaultQueryParameter(null); + queryParameter2.anyChars().literal("bar").anyChars(); + when(query.bindValue("constraint_1", queryParameter2)).thenReturn(query); + + DefaultQueryParameter queryParameter3 = new DefaultQueryParameter(null); + queryParameter3.literal("buz"); + when(query.bindValue("constraint_2", queryParameter3)).thenReturn(query); + + when(query.bindValue("filterType", NotificationFilterType.INCLUSIVE)).thenReturn(query); + + when(query.setWiki(wikiName)).thenReturn(query); + when(query.setOffset(offset.intValue())).thenReturn(query); + when(query.setLimit(limit)).thenReturn(query); + + List expectedList = List.of( + mock(NotificationFilterPreference.class, "filterPref1"), + mock(NotificationFilterPreference.class, "filterPref2"), + mock(NotificationFilterPreference.class, "filterPref3") + ); + when(query.execute()).thenReturn(expectedList); + assertEquals(expectedList, this.queryHelper.getFilterPreferences(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).bindValue("constraint_0", queryParameter1); + verify(query).bindValue("constraint_1", queryParameter2); + verify(query).bindValue("constraint_2", queryParameter3); + verify(query).bindValue("filterType", NotificationFilterType.INCLUSIVE); + verify(query).setWiki(wikiName); + verify(query).setOffset(offset.intValue()); + verify(query).setLimit(limit); + } + + @Test + void countFilterPreferencesFilterSort() throws QueryException, LiveDataException + { + String owner = "xwiki:XWiki.Foo"; + String wikiName = "foo"; + Long offset = 3L; + int limit = 12; + WikiReference wikiReference = new WikiReference(wikiName); + + LiveDataQuery ldQuery = mock(LiveDataQuery.class); + when(ldQuery.getOffset()).thenReturn(offset); + when(ldQuery.getLimit()).thenReturn(limit); + + LiveDataQuery.Filter filter1 = mock(LiveDataQuery.Filter.class, "filter1"); + when(filter1.getProperty()).thenReturn("location"); + when(filter1.isMatchAll()).thenReturn(false); + LiveDataQuery.Constraint filter1Constraint1 = mock(LiveDataQuery.Constraint.class, "filter1Constraint1"); + when(filter1Constraint1.getOperator()).thenReturn("startsWith"); + when(filter1Constraint1.getValue()).thenReturn("foo"); + LiveDataQuery.Constraint filter1Constraint2 = mock(LiveDataQuery.Constraint.class, "filter1Constraint2"); + when(filter1Constraint2.getOperator()).thenReturn("contains"); + when(filter1Constraint2.getValue()).thenReturn("bar"); + LiveDataQuery.Constraint filter1Constraint3 = mock(LiveDataQuery.Constraint.class, "filter1Constraint3"); + when(filter1Constraint3.getOperator()).thenReturn("equals"); + when(filter1Constraint3.getValue()).thenReturn("buz"); + when(filter1.getConstraints()).thenReturn(List.of(filter1Constraint1, filter1Constraint2, filter1Constraint3)); + + LiveDataQuery.Filter filter2 = mock(LiveDataQuery.Filter.class, "filter2"); + when(filter2.getProperty()).thenReturn("eventTypes"); + LiveDataQuery.Constraint filter2Constraint = mock(LiveDataQuery.Constraint.class, "filter2Constraint"); + when(filter2Constraint.getOperator()).thenReturn("equals"); + when(filter2Constraint.getValue()).thenReturn("__ALL_EVENTS__"); + when(filter2.getConstraints()).thenReturn(List.of(filter2Constraint)); + + LiveDataQuery.Filter filter3 = mock(LiveDataQuery.Filter.class, "filter3"); + when(filter3.getProperty()).thenReturn("isEnabled"); + LiveDataQuery.Constraint filter3Constraint = mock(LiveDataQuery.Constraint.class, "filter3Constraint"); + when(filter3Constraint.getOperator()).thenReturn("equals"); + when(filter3Constraint.getValue()).thenReturn("true"); + when(filter3.getConstraints()).thenReturn(List.of(filter3Constraint)); + + LiveDataQuery.Filter filter4 = mock(LiveDataQuery.Filter.class, "filter4"); + when(filter4.getProperty()).thenReturn("scope"); + LiveDataQuery.Constraint filter4Constraint = mock(LiveDataQuery.Constraint.class, "filter4Constraint"); + when(filter4Constraint.getOperator()).thenReturn("equals"); + when(filter4Constraint.getValue()).thenReturn("PAGE"); + when(filter4.getConstraints()).thenReturn(List.of(filter4Constraint)); + + LiveDataQuery.Filter filter5 = mock(LiveDataQuery.Filter.class, "filter5"); + when(filter5.getProperty()).thenReturn("filterType"); + LiveDataQuery.Constraint filter5Constraint = mock(LiveDataQuery.Constraint.class, "filter5Constraint"); + when(filter5Constraint.getOperator()).thenReturn("equals"); + when(filter5Constraint.getValue()).thenReturn("INCLUSIVE"); + when(filter5.getConstraints()).thenReturn(List.of(filter5Constraint)); + + when(ldQuery.getFilters()).thenReturn(List.of( + filter1, + filter2, + filter3, + filter4, + filter5 + )); + + LiveDataQuery.SortEntry sortEntry1 = mock(LiveDataQuery.SortEntry.class, "sortEntry1"); + when(sortEntry1.isDescending()).thenReturn(false); + when(sortEntry1.getProperty()).thenReturn("scope"); + + LiveDataQuery.SortEntry sortEntry2 = mock(LiveDataQuery.SortEntry.class, "sortEntry2"); + when(sortEntry2.isDescending()).thenReturn(true); + when(sortEntry2.getProperty()).thenReturn("isEnabled"); + + LiveDataQuery.SortEntry sortEntry3 = mock(LiveDataQuery.SortEntry.class, "sortEntry3"); + when(sortEntry3.isDescending()).thenReturn(true); + when(sortEntry3.getProperty()).thenReturn("notificationFormats"); + + when(ldQuery.getSort()).thenReturn(List.of(sortEntry1, sortEntry2, sortEntry3)); + + String queryString = "select count(nfp.id) from DefaultNotificationFilterPreference nfp where owner = :owner " + + "and ((nfp.pageOnly like :constraint_0 or nfp.page like :constraint_0 or nfp.wiki like :constraint_0 or " + + "nfp.user like :constraint_0) or (nfp.pageOnly like :constraint_1 or nfp.page like :constraint_1 or " + + "nfp.wiki like :constraint_1 or nfp.user like :constraint_1) or (nfp.pageOnly = :constraint_2 or " + + "nfp.page = :constraint_2 or nfp.wiki = :constraint_2 or nfp.user = :constraint_2)) and " + + "length(nfp.allEventTypes) = 0 and nfp.enabled = 1 and length(nfp.pageOnly) > 0 and " + + "nfp.filterType = :filterType"; + + Query query = mock(Query.class); + when(this.queryManager.createQuery(queryString, Query.HQL)).thenReturn(query); + when(query.bindValue("owner", owner)).thenReturn(query); + + DefaultQueryParameter queryParameter1 = new DefaultQueryParameter(null); + queryParameter1.literal("foo").anyChars(); + when(query.bindValue("constraint_0", queryParameter1)).thenReturn(query); + + DefaultQueryParameter queryParameter2 = new DefaultQueryParameter(null); + queryParameter2.anyChars().literal("bar").anyChars(); + when(query.bindValue("constraint_1", queryParameter2)).thenReturn(query); + + DefaultQueryParameter queryParameter3 = new DefaultQueryParameter(null); + queryParameter3.literal("buz"); + when(query.bindValue("constraint_2", queryParameter3)).thenReturn(query); + + when(query.bindValue("filterType", NotificationFilterType.INCLUSIVE)).thenReturn(query); + + when(query.setWiki(wikiName)).thenReturn(query); + + when(query.execute()).thenReturn(List.of(3L)); + assertEquals(3L, this.queryHelper.countTotalFilters(ldQuery, owner, wikiReference)); + verify(query).bindValue("owner", owner); + verify(query).bindValue("constraint_0", queryParameter1); + verify(query).bindValue("constraint_1", queryParameter2); + verify(query).bindValue("constraint_2", queryParameter3); + verify(query).bindValue("filterType", NotificationFilterType.INCLUSIVE); + verify(query).setWiki(wikiName); + verify(query, never()).setOffset(anyInt()); + verify(query, never()).setLimit(anyInt()); + } } \ No newline at end of file From 16cb25be8a9a8001890ecdba2e0b68345c00062b Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Mon, 18 Mar 2024 17:06:15 +0100 Subject: [PATCH 22/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Provide more unit tests --- ...cationCustomFiltersLiveDataEntryStore.java | 4 +- ...onCustomFiltersLiveDataEntryStoreTest.java | 484 ++++++++++++++++++ 2 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStoreTest.java diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index 615704c486e0..ad68ecc5822c 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -116,7 +116,7 @@ public Optional> get(Object entryId) throws LiveDataExceptio } private Map getPreferenceInformation(NotificationFilterPreference filterPreference) - throws NotificationException, LiveDataException + throws LiveDataException { NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = getScope(filterPreference); @@ -245,7 +245,7 @@ public LiveData get(LiveDataQuery query) throws LiveDataException for (NotificationFilterPreference notificationFilterPreference : filterPreferences) { entries.add(getPreferenceInformation(notificationFilterPreference)); } - } catch (QueryException | NotificationException e) { + } catch (QueryException e) { throw new LiveDataException("Error when querying notification filter preferences", e); } return liveData; diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStoreTest.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStoreTest.java new file mode 100644 index 000000000000..54a9c8efacb9 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/test/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStoreTest.java @@ -0,0 +1,484 @@ +/* + * 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.custom; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.inject.Named; +import javax.inject.Provider; +import javax.script.ScriptContext; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.livedata.LiveData; +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.SpaceReference; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.notifications.NotificationException; +import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.NotificationFilter; +import org.xwiki.notifications.filters.NotificationFilterManager; +import org.xwiki.notifications.filters.NotificationFilterPreference; +import org.xwiki.notifications.filters.NotificationFilterType; +import org.xwiki.notifications.filters.internal.DefaultNotificationFilterPreference; +import org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore; +import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; +import org.xwiki.query.QueryException; +import org.xwiki.rendering.block.Block; +import org.xwiki.rendering.renderer.BlockRenderer; +import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; +import org.xwiki.script.ScriptContextManager; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; +import org.xwiki.template.TemplateManager; +import org.xwiki.test.annotation.BeforeComponent; +import org.xwiki.test.junit5.mockito.ComponentTest; +import org.xwiki.test.junit5.mockito.InjectMockComponents; +import org.xwiki.test.junit5.mockito.MockComponent; +import org.xwiki.test.mockito.MockitoComponentManager; + +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.test.reference.ReferenceComponentList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link NotificationCustomFiltersLiveDataEntryStore}. + * + * @version $Id$ + * @since 16.3.0RC1 + */ +@ComponentTest +@ReferenceComponentList +class NotificationCustomFiltersLiveDataEntryStoreTest +{ + @InjectMockComponents + private NotificationCustomFiltersLiveDataEntryStore entryStore; + + @MockComponent + private ContextualAuthorizationManager contextualAuthorizationManager; + + @MockComponent + private NotificationFilterLiveDataTranslationHelper translationHelper; + + @MockComponent + private Provider contextProvider; + + @MockComponent + private NotificationFilterPreferenceStore notificationFilterPreferenceStore; + + @MockComponent + private NotificationFilterManager notificationFilterManager; + + @MockComponent + @Named("html/5.0") + private BlockRenderer blockRenderer; + + @MockComponent + private TemplateManager templateManager; + + @MockComponent + private ScriptContextManager scriptContextManager; + + @MockComponent + private NotificationCustomFiltersQueryHelper queryHelper; + + private XWikiContext context; + + @BeforeComponent + void beforeComponent(MockitoComponentManager componentManager) throws Exception + { + componentManager.registerComponent(ComponentManager.class, "context", componentManager); + } + + @BeforeEach + void beforeEach() + { + this.context = mock(XWikiContext.class); + when(this.contextProvider.get()).thenReturn(this.context); + + when(this.translationHelper.getFormatTranslation(any())) + .thenAnswer(invocationOnMock -> "Format " + invocationOnMock.getArgument(0)); + when(this.translationHelper.getScopeTranslation(any())) + .thenAnswer(invocationOnMock -> "Scope " + invocationOnMock.getArgument(0)); + when(this.translationHelper.getFilterTypeTranslation(any())) + .thenAnswer(invocationOnMock -> "FilterType " + invocationOnMock.getArgument(0)); + when(this.translationHelper.getAllEventTypesTranslation()).thenReturn("All event types"); + when(this.translationHelper.getEventTypeTranslation(any())) + .thenAnswer(invocationOnMock -> "EventType " + invocationOnMock.getArgument(0)); + } + + @Test + void getMissingTarget() + { + LiveDataQuery query = mock(LiveDataQuery.class); + LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); + when(query.getSource()).thenReturn(source); + when(source.getParameters()).thenReturn(Map.of()); + LiveDataException liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(query)); + assertEquals("The target source parameter is mandatory.", liveDataException.getMessage()); + } + + @Test + void getBadAuthorization() throws LiveDataException + { + LiveDataQuery query = mock(LiveDataQuery.class); + LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); + when(query.getSource()).thenReturn(source); + when(source.getParameters()).thenReturn(Map.of( + "target", "wiki", + "wiki", "foo" + )); + DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "Foo"); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + when(context.getUserReference()).thenReturn(userDoc); + LiveDataException liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(query)); + assertEquals("You don't have rights to access those information.", liveDataException.getMessage()); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(true); + LiveData emptyLiveData = new LiveData(); + assertEquals(emptyLiveData, this.entryStore.get(query)); + + when(source.getParameters()).thenReturn(Map.of( + "target", "user", + "user", "xwiki:XWiki.Bar" + )); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(query)); + assertEquals("You don't have rights to access those information.", liveDataException.getMessage()); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(true); + assertEquals(emptyLiveData, this.entryStore.get(query)); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + when(source.getParameters()).thenReturn(Map.of( + "target", "user", + "user", "xwiki:XWiki.Foo" + )); + assertEquals(emptyLiveData, this.entryStore.get(query)); + } + + @Test + void getEntry(MockitoComponentManager componentManager) throws Exception + { + String entryId = "entryId"; + WikiReference wikiReference = new WikiReference("foo"); + when(this.context.getWikiReference()).thenReturn(wikiReference); + assertEquals(Optional.empty(), this.entryStore.get(entryId)); + DefaultNotificationFilterPreference notificationFilterPreference = + mock(DefaultNotificationFilterPreference.class); + when(this.notificationFilterPreferenceStore.getFilterPreference(entryId, wikiReference)) + .thenReturn(Optional.of(notificationFilterPreference)); + DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "Foo"); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + when(context.getUserReference()).thenReturn(userDoc); + + LiveDataException liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(entryId)); + assertEquals("You don't have rights to access those information.", liveDataException.getMessage()); + + String filterId = "watchlist_id432"; + when(notificationFilterPreference.getId()).thenReturn(filterId); + when(notificationFilterPreference.getEventTypes()).thenReturn(Set.of()); + when(notificationFilterPreference.getNotificationFormats()) + .thenReturn(Set.of(NotificationFormat.ALERT, NotificationFormat.EMAIL)); + String page = "foo:Space.Page"; + SpaceReference spaceReference = new SpaceReference("foo", List.of("Space", "Page")); + when(notificationFilterPreference.getPage()).thenReturn(page); + when(notificationFilterPreference.getFilterType()).thenReturn(NotificationFilterType.EXCLUSIVE); + when(notificationFilterPreference.isEnabled()).thenReturn(true); + + ScriptContext scriptContext = mock(ScriptContext.class); + when(this.scriptContextManager.getCurrentScriptContext()).thenReturn(scriptContext); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(true); + String filterName = "scopeFilter"; + when(notificationFilterPreference.getFilterName()).thenReturn(filterName); + + liveDataException = assertThrows(LiveDataException.class, () -> this.entryStore.get(entryId)); + assertEquals("Cannot find NotificationFilter component for preference named [scopeFilter]", + liveDataException.getMessage()); + + NotificationFilter notificationFilter = + componentManager.registerMockComponent(NotificationFilter.class, filterName); + + String displayLocation = "Location hierarchy"; + when(this.templateManager.renderNoException("notification/filters/livedatalocation.vm")) + .thenReturn(displayLocation); + Block block = mock(Block.class); + when(this.notificationFilterManager.displayFilter(notificationFilter, notificationFilterPreference)) + .thenReturn(block); + String displayData = "Scope and location here"; + doAnswer(invocationOnMock -> { + DefaultWikiPrinter wikiPrinter = invocationOnMock.getArgument(1); + wikiPrinter.print(displayData); + return null; + }).when(this.blockRenderer).render(eq(block), any()); + + Map expectedResult = new LinkedHashMap<>(); + expectedResult.put("filterPreferenceId", filterId); + expectedResult.put("eventTypes", Map.of( + "extraClass", "list-unstyled", + "items", List.of("All event types") + )); + expectedResult.put("notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT", "Format EMAIL") + )); + expectedResult.put("scope", Map.of( + "icon", "chart-organisation", + "name", "Scope SPACE" + )); + + expectedResult.put("location", displayLocation); + expectedResult.put("display", displayData); + expectedResult.put("filterType", "FilterType EXCLUSIVE"); + expectedResult.put("isEnabled_checked", true); + expectedResult.put("isEnabled_disabled", true); + expectedResult.put("isEnabled_data", Map.of( + "preferenceId", filterId + )); + expectedResult.put("doc_hasdelete", true); + + assertEquals(Optional.of(expectedResult), this.entryStore.get(entryId)); + + verify(scriptContext, times(2)).setAttribute("location", new EntityReference(spaceReference), + ScriptContext.ENGINE_SCOPE); + + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(false); + when(notificationFilterPreference.getOwner()).thenReturn("xwiki:XWiki.Foo"); + assertEquals(Optional.of(expectedResult), this.entryStore.get(entryId)); + } + + @Test + void getEntries(MockitoComponentManager componentManager) throws Exception + { + LiveDataQuery query = mock(LiveDataQuery.class); + LiveDataQuery.Source source = mock(LiveDataQuery.Source.class); + when(query.getSource()).thenReturn(source); + String owner = "foo"; + when(source.getParameters()).thenReturn(Map.of( + "target", "wiki", + "wiki", owner + )); + DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "Foo"); + when(this.contextualAuthorizationManager.hasAccess(Right.ADMIN)).thenReturn(true); + when(context.getUserReference()).thenReturn(userDoc); + WikiReference wikiReference = new WikiReference(owner); + Long count = 12L; + when(this.queryHelper.countTotalFilters(query, owner, wikiReference)).thenReturn(count); + + NotificationFilterPreference filterPref1 = mock(NotificationFilterPreference.class); + NotificationFilterPreference filterPref2 = mock(NotificationFilterPreference.class); + NotificationFilterPreference filterPref3 = mock(NotificationFilterPreference.class); + when(this.queryHelper.getFilterPreferences(query, owner, wikiReference)).thenReturn(List.of( + filterPref1, + filterPref2, + filterPref3 + )); + + String filterId1 = "id432"; + when(filterPref1.getId()).thenReturn(filterId1); + when(filterPref1.getEventTypes()).thenReturn(Set.of("event foo")); + when(filterPref1.getNotificationFormats()).thenReturn(Set.of(NotificationFormat.EMAIL)); + String user = "xwiki:XWiki.User1"; + //DocumentReference user1DocRef = new DocumentReference("xwiki", "XWiki", "User1"); + when(filterPref1.getUser()).thenReturn(user); + when(filterPref1.getFilterType()).thenReturn(NotificationFilterType.INCLUSIVE); + when(filterPref1.isEnabled()).thenReturn(false); + + String filterId2 = "bka_id432"; + when(filterPref2.getId()).thenReturn(filterId2); + when(filterPref2.getEventTypes()).thenReturn(Set.of()); + when(filterPref2.getNotificationFormats()).thenReturn(Set.of(NotificationFormat.ALERT)); + String wikiFilter2 = "bla"; + //WikiReference wikiReferenceFilter = new WikiReference(wikiFilter2); + when(filterPref2.getWiki()).thenReturn(wikiFilter2); + when(filterPref2.getFilterType()).thenReturn(NotificationFilterType.INCLUSIVE); + when(filterPref2.isEnabled()).thenReturn(true); + + String filterId3 = "444564"; + when(filterPref3.getId()).thenReturn(filterId3); + when(filterPref3.getEventTypes()).thenReturn(Set.of("event foo", "bar", "buz")); + when(filterPref3.getNotificationFormats()).thenReturn(Set.of(NotificationFormat.EMAIL, + NotificationFormat.ALERT)); + String pageOnly = "xwiki:XWiki.User1"; + DocumentReference pageOnlyRef = new DocumentReference("xwiki", "XWiki", "User1"); + when(filterPref3.getPageOnly()).thenReturn(pageOnly); + when(filterPref3.getFilterType()).thenReturn(NotificationFilterType.EXCLUSIVE); + when(filterPref3.isEnabled()).thenReturn(true); + + ScriptContext scriptContext1 = mock(ScriptContext.class, "scriptContext1"); + ScriptContext scriptContext2 = mock(ScriptContext.class, "scriptContext1"); + ScriptContext scriptContext3 = mock(ScriptContext.class, "scriptContext1"); + when(this.scriptContextManager.getCurrentScriptContext()).thenReturn( + scriptContext1, + scriptContext2, + scriptContext3 + ); + + String displayLocation1 = "Location hierarchy 1"; + String displayLocation2 = "Location hierarchy 2"; + String displayLocation3 = "Location hierarchy 3"; + when(this.templateManager.renderNoException("notification/filters/livedatalocation.vm")).thenReturn( + displayLocation1, + displayLocation2, + displayLocation3 + ); + + String filterName1 = "filter1"; + String filterName2 = "filter2"; + NotificationFilter notificationFilter1 = + componentManager.registerMockComponent(NotificationFilter.class, filterName1); + NotificationFilter notificationFilter2 = + componentManager.registerMockComponent(NotificationFilter.class, filterName2); + + when(filterPref1.getFilterName()).thenReturn(filterName1); + when(filterPref2.getFilterName()).thenReturn(filterName2); + when(filterPref3.getFilterName()).thenReturn(filterName2); + + Block block1 = mock(Block.class, "block1"); + Block block2 = mock(Block.class, "block2"); + Block block3 = mock(Block.class, "block3"); + + when(this.notificationFilterManager.displayFilter(notificationFilter1, filterPref1)) + .thenReturn(block1); + when(this.notificationFilterManager.displayFilter(notificationFilter2, filterPref2)) + .thenReturn(block2); + when(this.notificationFilterManager.displayFilter(notificationFilter2, filterPref3)) + .thenReturn(block3); + + String displayData1 = "Scope and location here 1"; + String displayData2 = "Scope and location here 2"; + String displayData3 = "Scope and location here 3"; + doAnswer(invocationOnMock -> { + DefaultWikiPrinter wikiPrinter = invocationOnMock.getArgument(1); + wikiPrinter.print(displayData1); + return null; + }).when(this.blockRenderer).render(eq(block1), any()); + + doAnswer(invocationOnMock -> { + DefaultWikiPrinter wikiPrinter = invocationOnMock.getArgument(1); + wikiPrinter.print(displayData2); + return null; + }).when(this.blockRenderer).render(eq(block2), any()); + + doAnswer(invocationOnMock -> { + DefaultWikiPrinter wikiPrinter = invocationOnMock.getArgument(1); + wikiPrinter.print(displayData3); + return null; + }).when(this.blockRenderer).render(eq(block3), any()); + + Map expectedResult1 = new LinkedHashMap<>(); + expectedResult1.put("filterPreferenceId", filterId1); + expectedResult1.put("eventTypes", Map.of( + "extraClass", "list-unstyled", + "items", List.of("EventType event foo") + )); + expectedResult1.put("notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format EMAIL") + )); + expectedResult1.put("scope", Map.of( + "icon", "user", + "name", "Scope USER" + )); + expectedResult1.put("location", displayLocation1); + expectedResult1.put("display", displayData1); + expectedResult1.put("filterType", "FilterType INCLUSIVE"); + expectedResult1.put("isEnabled_checked", false); + expectedResult1.put("isEnabled_disabled", false); + expectedResult1.put("isEnabled_data", Map.of( + "preferenceId", filterId1 + )); + expectedResult1.put("doc_hasdelete", true); + + Map expectedResult2 = new LinkedHashMap<>(); + expectedResult2.put("filterPreferenceId", filterId2); + expectedResult2.put("eventTypes", Map.of( + "extraClass", "list-unstyled", + "items", List.of("All event types") + )); + expectedResult2.put("notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT") + )); + expectedResult2.put("scope", Map.of( + "icon", "wiki", + "name", "Scope WIKI" + )); + expectedResult2.put("location", displayLocation2); + expectedResult2.put("display", displayData2); + expectedResult2.put("filterType", "FilterType INCLUSIVE"); + expectedResult2.put("isEnabled_checked", true); + expectedResult2.put("isEnabled_disabled", false); + expectedResult2.put("isEnabled_data", Map.of( + "preferenceId", filterId2 + )); + expectedResult2.put("doc_hasdelete", true); + + Map expectedResult3 = new LinkedHashMap<>(); + expectedResult3.put("filterPreferenceId", filterId3); + expectedResult3.put("eventTypes", Map.of( + "extraClass", "list-unstyled", + "items", List.of("EventType bar", "EventType buz", "EventType event foo") + )); + expectedResult3.put("notificationFormats", Map.of( + "extraClass", "list-unstyled", + "items", List.of("Format ALERT", "Format EMAIL") + )); + expectedResult3.put("scope", Map.of( + "icon", "page", + "name", "Scope PAGE" + )); + expectedResult3.put("location", displayLocation3); + expectedResult3.put("display", displayData3); + expectedResult3.put("filterType", "FilterType EXCLUSIVE"); + expectedResult3.put("isEnabled_checked", true); + expectedResult3.put("isEnabled_disabled", false); + expectedResult3.put("isEnabled_data", Map.of( + "preferenceId", filterId3 + )); + expectedResult3.put("doc_hasdelete", true); + + LiveData liveData = new LiveData(); + liveData.setCount(count); + liveData.getEntries().addAll(List.of(expectedResult1, expectedResult2, expectedResult3)); + + assertEquals(liveData, this.entryStore.get(query)); + } +} \ No newline at end of file From ee34d3203533cb44d602a8ac35b78ca846e2e478 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Mon, 18 Mar 2024 17:12:03 +0100 Subject: [PATCH 23/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Fix since values * Introduce integration tests * Introduce page objects for LiveData panels * Fix UserProfile PO to allow following user --- .../AbstractLiveDataAdvancedPanelElement.java | 78 +++++++++ .../livedata/test/po/FiltersPanelElement.java | 52 ++++++ .../livedata/test/po/LiveDataElement.java | 57 ++++++- .../livedata/test/po/SortPanelElement.java | 52 ++++++ .../livedata/test/po/TableLayoutElement.java | 22 +++ ...oggleableNotificationFilterActivation.java | 2 +- .../NotificationFilterPreferenceStore.java | 2 +- ...leFilterPreferenceDocumentInitializer.java | 3 + ...tNotificationFilterLiveDataEntryStore.java | 2 +- ...cationFilterLiveDataTranslationHelper.java | 2 +- ...mFiltersLiveDataConfigurationProvider.java | 1 + ...mFiltersLiveDataConfigurationResolver.java | 1 + ...cationCustomFiltersLiveDataEntryStore.java | 1 + ...iltersLiveDataPropertyDescriptorStore.java | 1 + ...tificationCustomFiltersLiveDataSource.java | 2 +- .../NotificationCustomFiltersQueryHelper.java | 2 +- ...mFiltersLiveDataConfigurationProvider.java | 1 + ...mFiltersLiveDataConfigurationResolver.java | 1 + ...cationSystemFiltersLiveDataEntryStore.java | 1 + ...iltersLiveDataPropertyDescriptorStore.java | 1 + ...tificationSystemFiltersLiveDataSource.java | 2 +- .../test/ui/NotificationsSettingsIT.java | 138 +++++++++++++--- .../po/AbstractNotificationsSettingsPage.java | 16 +- ...ationFilterPreferencesLiveDataElement.java | 150 ++++++++++++++++++ .../user/test/po/ProfileUserProfilePage.java | 21 ++- 25 files changed, 560 insertions(+), 51 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/AbstractLiveDataAdvancedPanelElement.java create mode 100644 xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/FiltersPanelElement.java create mode 100644 xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/SortPanelElement.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/CustomNotificationFilterPreferencesLiveDataElement.java diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/AbstractLiveDataAdvancedPanelElement.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/AbstractLiveDataAdvancedPanelElement.java new file mode 100644 index 000000000000..3b636256930f --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/AbstractLiveDataAdvancedPanelElement.java @@ -0,0 +1,78 @@ +/* + * 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.livedata.test.po; + +import org.openqa.selenium.By; +import org.openqa.selenium.InvalidElementStateException; +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.xwiki.test.ui.po.BaseElement; + +/** + * Abstract class representing different type of advanced panels in livedata. + * + * @version $Id$ + * @since 16.3.0RC1 + */ +public abstract class AbstractLiveDataAdvancedPanelElement extends BaseElement +{ + protected final WebElement container; + protected final LiveDataElement liveData; + + /** + * Default constructor. + * @param liveData the livedata the panel belongs to. + * @param container the container of the panel. + */ + public AbstractLiveDataAdvancedPanelElement(LiveDataElement liveData, WebElement container) + { + this.liveData = liveData; + this.container = container; + } + + /** + * Close the panel. + */ + public void closePanel() + { + getDriver().findElementWithoutWaiting(this.container, By.className("close-button")).click(); + } + + /** + * Allow to click on links that are only visible when the mouse is on them (e.g. delete icons of filters) + * @param linkIdentifiers the identifier to find the link element in the dom. + */ + protected void clickOnMouseOverLinks(By linkContainer, By linkIdentifiers) + { + getDriver().findElementsWithoutWaiting(this.container, linkContainer) + .forEach(webElement -> { + // First we move to the link container to make the link appearing + getDriver().createActions().moveToElement(webElement).build().perform(); + WebElement linkElement = getDriver().findElementWithoutWaiting(webElement, linkIdentifiers); + if (!linkElement.isDisplayed()) { + throw new InvalidElementStateException(String.format("The link [%s] should be displayed.", + linkElement)); + } + // Click on the actual link + getDriver().createActions().moveToElement(linkElement).click().build().perform(); + }); + } +} diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/FiltersPanelElement.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/FiltersPanelElement.java new file mode 100644 index 000000000000..adabb75304a7 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/FiltersPanelElement.java @@ -0,0 +1,52 @@ +/* + * 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.livedata.test.po; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +/** + * Represents the advanced panel for filtering columns. + * + * @version $Id$ + * @since 16.3.0RC1 + */ +public class FiltersPanelElement extends AbstractLiveDataAdvancedPanelElement +{ + /** + * Default constructor. + * @param liveData the livedata of the panel. + * @param container the container of the panel. + */ + public FiltersPanelElement(LiveDataElement liveData, WebElement container) + { + super(liveData, container); + } + + /** + * Remove all filters. + */ + public void clearAllFilters() + { + this.clickOnMouseOverLinks(By.className("filter-group-title"), By.className("delete-filter-group")); + this.liveData.waitUntilReady(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java index 67219c5bf6e1..ebb8af055b8f 100644 --- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java +++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java @@ -104,6 +104,13 @@ public boolean isReady() return isVueLoaded() && areComponentsLoaded(); } + private WebElement openDropDownMenu() + { + WebElement dropdownMenu = getRootElement().findElement(By.cssSelector(".livedata-dropdown-menu ")); + dropdownMenu.click(); + return dropdownMenu; + } + /** * Click on the refresh button from the actions menu. * @@ -111,9 +118,7 @@ public boolean isReady() */ public void refresh() { - WebElement dropdownMenu = getRootElement().findElement(By.cssSelector(".livedata-dropdown-menu ")); - dropdownMenu.click(); - dropdownMenu.findElement(By.cssSelector(".livedata-action-refresh")).click(); + openDropDownMenu().findElement(By.cssSelector(".livedata-action-refresh")).click(); } /** @@ -139,7 +144,7 @@ public LiveDataElement setPagination(int paginationNumber) return this; } - private void waitUntilReady() + public void waitUntilReady() { getDriver().waitUntilCondition(input -> isVueLoaded()); @@ -180,4 +185,48 @@ private WebElement getRootElement() { return getDriver().findElement(By.id(this.id)); } + + /** + * Open the panel for advanced filter and returns it. + * @return an instance of {@link FiltersPanelElement} once it's opened. + * @since 16.3.0RC1 + */ + public FiltersPanelElement openFiltersPanel() + { + openDropDownMenu().findElement(By.linkText("Filter...")).click(); + return new FiltersPanelElement(this, + getRootElement().findElement(By.className("livedata-advanced-panel-filter"))); + } + + /** + * Open the panel for advanced sorting and returns it. + * @return an instance of {@link SortPanelElement} once it's opened. + * @since 16.3.0RC1 + */ + public SortPanelElement openSortPanel() + { + openDropDownMenu().findElement(By.linkText("Sort...")).click(); + return new SortPanelElement(this, + getRootElement().findElement(By.className("livedata-advanced-panel-sort"))); + } + + /** + * Clear all custom sorting that might have been put. + */ + public void clearAllSort() + { + SortPanelElement sortPanelElement = openSortPanel(); + sortPanelElement.clearAllSort(); + sortPanelElement.closePanel(); + } + + /** + * Clear all custom filters that might have been put. + */ + public void clearAllFilters() + { + FiltersPanelElement filtersPanelElement = openFiltersPanel(); + filtersPanelElement.clearAllFilters(); + filtersPanelElement.closePanel(); + } } diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/SortPanelElement.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/SortPanelElement.java new file mode 100644 index 000000000000..e6bd12e3ba1e --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/SortPanelElement.java @@ -0,0 +1,52 @@ +/* + * 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.livedata.test.po; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +/** + * Represents the advanced panel for sorting columns. + * + * @version $Id$ + * @since 16.3.0RC1 + */ +public class SortPanelElement extends AbstractLiveDataAdvancedPanelElement +{ + /** + * Default constructor. + * @param liveData the livedata of the panel. + * @param container the container of the panel. + */ + public SortPanelElement(LiveDataElement liveData, WebElement container) + { + super(liveData, container); + } + + /** + * Remove all sorting on all columns. + */ + public void clearAllSort() + { + this.clickOnMouseOverLinks(By.className("sort-entry"), By.className("delete-sort")); + this.liveData.waitUntilReady(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/TableLayoutElement.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/TableLayoutElement.java index 9f0162ec7ebd..ead6d9334b3c 100644 --- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/TableLayoutElement.java +++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/TableLayoutElement.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.http.NameValuePair; @@ -76,6 +77,9 @@ public class TableLayoutElement extends BaseElement private final LiveDataElement liveData; + private static final Pattern PAGINATION_SENTENCE_PATTERN = + Pattern.compile("^Entries (?\\d+) - (?\\d+) out of (?\\d+)$"); + /** * @return the list of rows {@link WebElement}s * @since 16.1.0RC1 @@ -688,6 +692,24 @@ public Set getPaginationSizes() .map(it -> it.getAttribute("value")).collect(Collectors.toSet()); } + private String getPaginationEntriesString() + { + return getRoot().findElement(By.className("pagination-current-entries")).getText().trim(); + } + + private java.util.regex.Matcher getPaginationMatcher() + { + return PAGINATION_SENTENCE_PATTERN.matcher(getPaginationEntriesString()); + } + + public long getTotalEntries() + { + java.util.regex.Matcher paginationMatcher = getPaginationMatcher(); + if (!paginationMatcher.matches()) { + throw new IllegalStateException("Matcher does not match: " + getPaginationEntriesString()); + } + return Long.parseLong(paginationMatcher.group("totalEntries")); + } /** * Clicks on an action button identified by its name, on a given row. diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java index 0a784ea35c91..a2997b58eb8c 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-api/src/main/java/org/xwiki/notifications/filters/internal/ToggleableNotificationFilterActivation.java @@ -31,7 +31,7 @@ * Provide information about the activation of {@link ToggleableNotificationFilter}. * * @version $Id$ - * @since 16.2.0RC1 + * @since 16.3.0RC1 */ public class ToggleableNotificationFilterActivation implements Serializable { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java index f23dc2f51aa4..65877d9e7e91 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/NotificationFilterPreferenceStore.java @@ -86,7 +86,7 @@ public class NotificationFilterPreferenceStore * @param filterPreferenceId a filter preference id * @return the corresponding preference or {@link Optional#empty()} if none can be found * @throws NotificationException if an error occurs - * @since 16.2.0RC1 + * @since 16.3.0RC1 */ public Optional getFilterPreference(String filterPreferenceId, WikiReference wikiReference) throws NotificationException diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java index ad35097efeb8..3fa418433484 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/ToggleableFilterPreferenceDocumentInitializer.java @@ -44,17 +44,20 @@ public class ToggleableFilterPreferenceDocumentInitializer extends AbstractManda { /** * Reference of the xclass. + * @since 16.3.0RC1 */ public static final LocalDocumentReference XCLASS = new LocalDocumentReference(List.of("XWiki", "Notifications", "Code"), "ToggleableFilterPreferenceClass"); /** * Name of field holding the filter name. + * @since 16.3.0RC1 */ public static final String FIELD_FILTER_NAME = "filterName"; /** * Name of the field holding the activation value. + * @since 16.3.0RC1 */ public static final String FIELD_IS_ENABLED = "isEnabled"; diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java index 499729fec22c..1703313b4aea 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java @@ -51,7 +51,7 @@ * {@link org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataEntryStore}. * * @version $Id$ - * @since 16.2.0RC1 + * @since 16.3.0RC1 */ public abstract class AbstractNotificationFilterLiveDataEntryStore implements LiveDataEntryStore { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index c7e80f2fb177..6ab7ca600251 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -42,7 +42,7 @@ * Helper for getting various translations for live data custom sources. * * @version $Id$ - * @since 16.2.0RC1 + * @since 16.3.0RC1 */ @Component(roles = NotificationFilterLiveDataTranslationHelper.class) @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java index 232c630f7dfe..ef65c2ef55e8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java @@ -48,6 +48,7 @@ /** * Configuration of the {@link NotificationCustomFiltersLiveDataSource}. * + * @since 16.3.0RC1 * @version $Id$ */ @Component diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationResolver.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationResolver.java index ef7de1f3ee7f..c074e4c3f34a 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationResolver.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationResolver.java @@ -33,6 +33,7 @@ /** * Needed component for the live data configuration. * + * @since 16.3.0RC1 * @version $Id$ */ @Component diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index ad68ecc5822c..69a2e56b207d 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -59,6 +59,7 @@ * Dedicated {@link LiveDataEntryStore} for the {@link NotificationCustomFiltersLiveDataSource}. * This component is in charge of performing the actual HQL queries to display the live data. * + * @since 16.3.0RC1 * @version $Id$ */ @Component diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java index 5c24883c2ea7..24db20625abc 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataPropertyDescriptorStore.java @@ -35,6 +35,7 @@ /** * Descriptor for the {@link NotificationCustomFiltersLiveDataSource}. * + * @since 16.3.0RC1 * @version $Id$ */ @Component diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java index 7c2fe6b2f904..688845b88be0 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataSource.java @@ -32,7 +32,7 @@ * Live data source for custom notification filters. * * @version $Id$ - * @since 16.2.0RC1 + * @since 16.3.0RC1 */ @Component @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java index 685c31ce5906..3da73ea489cf 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -48,7 +48,7 @@ * TODO: maybe this could be improved with some APIs to put in NotificationFilterPreferenceStore. * * @version $Id$ - * @since 16.2.0RC1 + * @since 16.3.0RC1 */ @Component(roles = NotificationCustomFiltersQueryHelper.class) @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java index f7ebdf1982ef..2cdba1c3d2ef 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java @@ -38,6 +38,7 @@ * Configuration of the {@link NotificationSystemFiltersLiveDataSource}. * * @version $Id$ + * @since 16.3.0RC1 */ @Component @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java index c1450ac098f5..45a566b83199 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationResolver.java @@ -34,6 +34,7 @@ * Needed component for the live data configuration. * * @version $Id$ + * @since 16.3.0RC1 */ @Component @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index cf53a41c63d7..9e23a4fb10d4 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -50,6 +50,7 @@ * This component is in charge of performing the actual HQL queries to display the live data. * * @version $Id$ + * @since 16.3.0RC1 */ @Component @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java index 86530eb74ccf..af86635a3c61 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataPropertyDescriptorStore.java @@ -36,6 +36,7 @@ * Descriptor for the {@link NotificationSystemFiltersLiveDataSource}. * * @version $Id$ + * @since 16.3.0RC1 */ @Component @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java index 46a9d6f730f5..9ea8d93cec7b 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataSource.java @@ -32,7 +32,7 @@ * Live data source for notification system filter preferences. * * @version $Id$ - * @since 16.2.0RC1 + * @since 16.3.0RC1 */ @Component @Singleton diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java index ed9c94fcd5d3..fa1cadba0c88 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java @@ -34,6 +34,7 @@ import org.xwiki.platform.notifications.test.po.NotificationsTrayPage; import org.xwiki.platform.notifications.test.po.NotificationsUserProfilePage; import org.xwiki.platform.notifications.test.po.preferences.ApplicationPreferences; +import org.xwiki.platform.notifications.test.po.preferences.CustomNotificationFilterPreferencesLiveDataElement; import org.xwiki.platform.notifications.test.po.preferences.filters.CustomNotificationFilterModal; import org.xwiki.platform.notifications.test.po.preferences.filters.CustomNotificationFilterPreference; import org.xwiki.platform.notifications.test.po.preferences.filters.SystemNotificationFilterPreference; @@ -45,6 +46,7 @@ import org.xwiki.test.ui.po.ViewPage; import org.xwiki.tree.test.po.TreeNodeElement; import org.xwiki.user.test.po.AbstractUserProfilePage; +import org.xwiki.user.test.po.ProfileUserProfilePage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -264,8 +266,10 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro // Go back to the preferences to ensure the filter has been created NotificationsUserProfilePage notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); + CustomNotificationFilterPreferencesLiveDataElement customPrefLiveData = + notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); List preferences = - notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(1, preferences.size()); // Filter 0 @@ -290,7 +294,9 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro // Go back to the preferences to ensure the filter has been deleted notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); - preferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData = + notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + preferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertTrue(preferences.isEmpty()); // back to the page @@ -305,7 +311,9 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro // Go back to the preferences to ensure the filter has been created notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); - preferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData = + notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + preferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(1, preferences.size()); // Filter 1 @@ -332,7 +340,9 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro // Go back to the preferences notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); - preferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData = + notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + preferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(2, preferences.size()); // Filter 2 @@ -349,7 +359,9 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro preferences.get(1).setEnabled(false); // Refresh the page notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); - preferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData = + notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + preferences = customPrefLiveData.getCustomNotificationFilterPreferences(); // Verify the change have been saved assertFalse(preferences.get(1).isEnabled()); @@ -364,9 +376,12 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro // Delete the filters notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); + customPrefLiveData = + notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + preferences = customPrefLiveData.getCustomNotificationFilterPreferences(); // The livetable page is refreshed so we need to load back the filter preferences - notificationsUserProfilePage.getCustomNotificationFilterPreferences().get(1).delete(); - notificationsUserProfilePage.getCustomNotificationFilterPreferences().get(0).delete(); + preferences.get(1).delete(); + preferences.get(0).delete(); // Verify it's all like the beginning testUtils.gotoPage(testReference.getLastSpaceReference().getName(), testReference.getName()); @@ -378,18 +393,22 @@ void filterAndWatchedPage(TestUtils testUtils, TestReference testReference) thro // Go back to the preferences notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); - preferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData = + notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + preferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertTrue(preferences.isEmpty()); } finally { // Clean up testUtils.rest().deletePage(testReference.getLastSpaceReference().getName(), testReference.getName()); NotificationsUserProfilePage p = NotificationsUserProfilePage.gotoPage(FIRST_USER_NAME); - List preferences = p.getCustomNotificationFilterPreferences(); + CustomNotificationFilterPreferencesLiveDataElement customPrefLiveData = + p.getCustomNotificationFilterPreferencesLiveData(); + List preferences = + customPrefLiveData.getCustomNotificationFilterPreferences(); while (!preferences.isEmpty()) { preferences.get(preferences.size() - 1).delete(); - // Reload the livetable - preferences = p.getCustomNotificationFilterPreferences(); + preferences = customPrefLiveData.getCustomNotificationFilterPreferences(); } } } @@ -562,10 +581,11 @@ void globalAndOtherUserSettings(TestUtils testUtils, TestReference testReference * - create a new user and check that the filter exist for it * - add a new filter to the user with admin user * - login with the new user and add another new filter + * - Add several new preferences and check the modal sort/filter operations */ @Test @Order(5) - void addCustomFilters(TestUtils testUtils) + void customFiltersAndLiveData(TestUtils testUtils, TestReference testReference) throws Exception { // Create pages for the filter locations SpaceReference lastSpaceReference = new SpaceReference("xwiki", NotificationsSettingsIT.class.getSimpleName()); @@ -579,8 +599,10 @@ void addCustomFilters(TestUtils testUtils) testUtils.loginAsSuperAdmin(); NotificationsAdministrationPage administrationPage = NotificationsAdministrationPage.gotoPage(); + CustomNotificationFilterPreferencesLiveDataElement customPrefLiveData = + administrationPage.getCustomNotificationFilterPreferencesLiveData(); List customNotificationFilterPreferences = - administrationPage.getCustomNotificationFilterPreferences(); + customPrefLiveData.getCustomNotificationFilterPreferences(); assertTrue(customNotificationFilterPreferences.isEmpty()); CustomNotificationFilterModal customNotificationFilterModal = administrationPage.clickAddCustomFilter(); @@ -605,7 +627,7 @@ void addCustomFilters(TestUtils testUtils) // check newly created filter customNotificationFilterPreferences = - administrationPage.getCustomNotificationFilterPreferences(); + customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(1, customNotificationFilterPreferences.size()); CustomNotificationFilterPreference filterPreference = customNotificationFilterPreferences.get(0); @@ -626,7 +648,8 @@ void addCustomFilters(TestUtils testUtils) // go to notification settings of new user and check that the custom filter exists there too NotificationsUserProfilePage notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(secondUserUsername); - customNotificationFilterPreferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData = notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + customNotificationFilterPreferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(1, customNotificationFilterPreferences.size()); filterPreference = customNotificationFilterPreferences.get(0); @@ -649,7 +672,7 @@ void addCustomFilters(TestUtils testUtils) customNotificationFilterModal.clickSubmit(); // Check the newly created filter - customNotificationFilterPreferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customNotificationFilterPreferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(2, customNotificationFilterPreferences.size()); filterPreference = customNotificationFilterPreferences.get(1); @@ -667,8 +690,9 @@ void addCustomFilters(TestUtils testUtils) // check the filters notificationsUserProfilePage = NotificationsUserProfilePage.gotoPage(secondUserUsername); + customPrefLiveData = notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); customNotificationFilterPreferences = - notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(2, customNotificationFilterPreferences.size()); filterPreference = @@ -704,7 +728,7 @@ void addCustomFilters(TestUtils testUtils) customNotificationFilterModal.clickSubmit(2); // check created filters - customNotificationFilterPreferences = notificationsUserProfilePage.getCustomNotificationFilterPreferences(); + customNotificationFilterPreferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(4, customNotificationFilterPreferences.size()); filterPreference = customNotificationFilterPreferences.get(2); @@ -724,6 +748,84 @@ void addCustomFilters(TestUtils testUtils) assertEquals(List.of("Alert"), filterPreference.getFormats()); // core.events.delete before core.events.update assertEquals(List.of("A page is deleted","A page is modified"), filterPreference.getEventTypes()); + + // follow a user + ProfileUserProfilePage userProfilePage = ProfileUserProfilePage.gotoPage(FIRST_USER_NAME); + assertFalse(userProfilePage.isFollowed()); + userProfilePage.toggleFollowButton(); + + notificationsUserProfilePage = + NotificationsUserProfilePage.gotoPage(secondUserUsername); + notificationsUserProfilePage.setAutoWatchMode(AbstractNotificationsSettingsPage.AutowatchMode.NEW); + + // Create multiple pages + for (int i = 0; i < 15; i++) { + DocumentReference pageRef = new DocumentReference("Page_" + i, testReference.getLastSpaceReference()); + testUtils.rest().savePage(pageRef, "Content of page " + i, "Title of page " + i ); + } + String pageSpaceName = + NotificationsSettingsIT.class.getSimpleName() + "." + testReference.getLastSpaceReference().getName(); + + notificationsUserProfilePage = + NotificationsUserProfilePage.gotoPage(secondUserUsername); + customPrefLiveData = notificationsUserProfilePage.getCustomNotificationFilterPreferencesLiveData(); + + // 4 filters created before following a user + // +1 when following the user + // +15 when creating the pages after setting autowatch + assertEquals(20, customPrefLiveData.getTableLayout().getTotalEntries()); + + customPrefLiveData.filterScope("USER"); + assertEquals(1, customPrefLiveData.getTableLayout().getTotalEntries()); + filterPreference = customPrefLiveData.getCustomNotificationFilterPreferences().get(0); + assertEquals("User", filterPreference.getScope()); + assertEquals(List.of("Alert", "Email"), filterPreference.getFormats()); + assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); + assertTrue(filterPreference.getEventTypes().isEmpty()); + assertEquals("XWiki." + FIRST_USER_NAME, filterPreference.getLocation()); + + customPrefLiveData.clearAllFilters(); + customPrefLiveData.filterLocation(testReference.getLastSpaceReference().getName()); + // Click twice to order descending + customPrefLiveData.sortLocation(); + customPrefLiveData.sortLocation(); + assertEquals(15, customPrefLiveData.getTableLayout().getTotalEntries()); + filterPreference = customPrefLiveData.getCustomNotificationFilterPreferences().get(0); + assertEquals("Page only", filterPreference.getScope()); + assertEquals(List.of("Alert", "Email"), filterPreference.getFormats()); + assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); + assertTrue(filterPreference.getEventTypes().isEmpty()); + // Order is alphabetical so Page_9 is > Page_15 + assertEquals(pageSpaceName + ".Page_9", filterPreference.getLocation()); + + customPrefLiveData.clearAllFilters(); + customPrefLiveData.clearAllSort(); + assertEquals(20, customPrefLiveData.getTableLayout().getTotalEntries()); + customPrefLiveData.filterFilterAction("INCLUSIVE"); + assertEquals(19, customPrefLiveData.getTableLayout().getTotalEntries()); + customPrefLiveData.filterLocation("2"); + // 3 possibles matches: Page2,Page_2,Page_12 + assertEquals(3, customPrefLiveData.getTableLayout().getTotalEntries()); + customPrefLiveData.sortFormats(); + customPrefLiveData.sortFormats(); + filterPreference = customPrefLiveData.getCustomNotificationFilterPreferences().get(0); + assertEquals("Page only", filterPreference.getScope()); + assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".Page2", filterPreference.getLocation()); + assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); + assertEquals(List.of("Alert"), filterPreference.getFormats()); + // core.events.delete before core.events.update + assertEquals(List.of("A page is deleted","A page is modified"), filterPreference.getEventTypes()); + + customPrefLiveData.filterFormat("EMAIL"); + assertEquals(2, customPrefLiveData.getTableLayout().getTotalEntries()); + customPrefLiveData.sortLocation(); + + filterPreference = customPrefLiveData.getCustomNotificationFilterPreferences().get(0); + assertEquals("Page only", filterPreference.getScope()); + assertEquals(List.of("Alert", "Email"), filterPreference.getFormats()); + assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); + assertTrue(filterPreference.getEventTypes().isEmpty()); + assertEquals(pageSpaceName + ".Page_12", filterPreference.getLocation()); } @Test diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/AbstractNotificationsSettingsPage.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/AbstractNotificationsSettingsPage.java index c05dabd9e89d..860bcb4841d2 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/AbstractNotificationsSettingsPage.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/AbstractNotificationsSettingsPage.java @@ -31,9 +31,9 @@ import org.xwiki.livedata.test.po.TableLayoutElement; import org.xwiki.platform.notifications.test.po.preferences.AbstractNotificationPreferences; import org.xwiki.platform.notifications.test.po.preferences.ApplicationPreferences; +import org.xwiki.platform.notifications.test.po.preferences.CustomNotificationFilterPreferencesLiveDataElement; import org.xwiki.platform.notifications.test.po.preferences.EventTypePreferences; import org.xwiki.platform.notifications.test.po.preferences.filters.CustomNotificationFilterModal; -import org.xwiki.platform.notifications.test.po.preferences.filters.CustomNotificationFilterPreference; import org.xwiki.platform.notifications.test.po.preferences.filters.SystemNotificationFilterPreference; import org.xwiki.test.ui.po.BootstrapSwitch; import org.xwiki.test.ui.po.Select; @@ -287,18 +287,12 @@ public List getSystemNotificationFilterPrefe } /** - * @return the custom notification filter preferences - * @since 13.2RC1 + * @return the livedata containing the custom filter preferences. + * @since 16.3.0RC1 */ - public List getCustomNotificationFilterPreferences() + public CustomNotificationFilterPreferencesLiveDataElement getCustomNotificationFilterPreferencesLiveData() { - List preferences = new ArrayList<>(); - LiveDataElement liveDataElement = - new LiveDataElement("notificationCustomFilterPreferencesLiveData"); - for (WebElement row : liveDataElement.getTableLayout().getRows()) { - preferences.add(new CustomNotificationFilterPreference(this, row, this.getDriver())); - } - return preferences; + return new CustomNotificationFilterPreferencesLiveDataElement(this); } /** diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/CustomNotificationFilterPreferencesLiveDataElement.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/CustomNotificationFilterPreferencesLiveDataElement.java new file mode 100644 index 000000000000..9ab468667b09 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-pageobjects/src/main/java/org/xwiki/platform/notifications/test/po/preferences/CustomNotificationFilterPreferencesLiveDataElement.java @@ -0,0 +1,150 @@ +/* + * 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.platform.notifications.test.po.preferences; + +import java.util.List; + +import org.xwiki.livedata.test.po.LiveDataElement; +import org.xwiki.platform.notifications.test.po.AbstractNotificationsSettingsPage; +import org.xwiki.platform.notifications.test.po.preferences.filters.CustomNotificationFilterPreference; + +/** + * Page object representing the live data which contains custom filter preferences. + * + * @version $Id$ + * @since 16.3.0RC1 + */ +public class CustomNotificationFilterPreferencesLiveDataElement extends LiveDataElement +{ + private static final String SCOPE_COLUMN = "Scope"; + private static final String LOCATION_COLUMN = "Location"; + private static final String FORMAT_COLUMN = "Formats"; + private static final String EVENT_TYPES_COLUMN = "Events"; + private static final String FILTER_ACTION_COLUMN = "Filter Action"; + private final AbstractNotificationsSettingsPage parentPage; + + /** + * Default constructor. + * + * @param parentPage the page holding the live data. + */ + public CustomNotificationFilterPreferencesLiveDataElement(AbstractNotificationsSettingsPage parentPage) + { + super("notificationCustomFilterPreferencesLiveData"); + this.parentPage = parentPage; + } + + /** + * @return the custom filter preferences entries. + */ + public List getCustomNotificationFilterPreferences() + { + return getTableLayout().getRows() + .stream() + .map(row -> new CustomNotificationFilterPreference(this.parentPage, row, this.getDriver())) + .toList(); + } + + /** + * Filter the scope column. + * @param scope the value to put in the filter. + */ + public void filterScope(String scope) + { + getTableLayout().filterColumn(SCOPE_COLUMN, scope); + } + + /** + * Sort the scope column. + */ + public void sortScope() + { + getTableLayout().sortBy(SCOPE_COLUMN); + } + + /** + * Filter the location column. + * @param location the value to put in the filter. + */ + public void filterLocation(String location) + { + getTableLayout().filterColumn(LOCATION_COLUMN, location); + } + + /** + * Sort the location column. + */ + public void sortLocation() + { + getTableLayout().sortBy(LOCATION_COLUMN); + } + + /** + * Filter the event type column. + * @param eventType the value to put in the filter. + */ + public void filterEventType(String eventType) + { + getTableLayout().filterColumn(EVENT_TYPES_COLUMN, eventType); + } + + /** + * Sort the event type column. + */ + public void sortEventType() + { + getTableLayout().sortBy(EVENT_TYPES_COLUMN); + } + + /** + * Filter the filter action column. + * @param filterAction the value to put in the filter. + */ + public void filterFilterAction(String filterAction) + { + getTableLayout().filterColumn(FILTER_ACTION_COLUMN, filterAction); + } + + /** + * Sort the filter action column. + */ + public void sortFilterAction() + { + getTableLayout().sortBy(FILTER_ACTION_COLUMN); + } + + /** + * Filter the format column. + * @param format the value to put in the filter. + */ + public void filterFormat(String format) + { + getTableLayout().filterColumn(FORMAT_COLUMN, format); + } + + /** + * Sort the format column. + */ + public void sortFormats() + { + getTableLayout().sortBy(FORMAT_COLUMN); + } +} diff --git a/xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-profile/xwiki-platform-user-profile-test/xwiki-platform-user-profile-test-pageobjects/src/main/java/org/xwiki/user/test/po/ProfileUserProfilePage.java b/xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-profile/xwiki-platform-user-profile-test/xwiki-platform-user-profile-test-pageobjects/src/main/java/org/xwiki/user/test/po/ProfileUserProfilePage.java index a35db2e44bb3..0182f79c45e6 100644 --- a/xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-profile/xwiki-platform-user-profile-test/xwiki-platform-user-profile-test-pageobjects/src/main/java/org/xwiki/user/test/po/ProfileUserProfilePage.java +++ b/xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-profile/xwiki-platform-user-profile-test/xwiki-platform-user-profile-test-pageobjects/src/main/java/org/xwiki/user/test/po/ProfileUserProfilePage.java @@ -68,8 +68,11 @@ public class ProfileUserProfilePage extends AbstractUserProfilePage @FindBy(xpath = "//div[@id='avatar']//img") private WebElement userAvatarImage; - @FindBy(css = ".activity-follow a") - private WebElement followUnfollowButton; + @FindBy(css = ".activity-follow .notificationWatchUserFollowing") + private WebElement followingContainer; + + @FindBy(css = ".activity-follow .notificationWatchUserNotFollowing") + private WebElement notFollowingContainer; @FindBy(id = "disable") private WebElement disableButton; @@ -161,19 +164,15 @@ public String getAvatarImageName() public boolean isFollowed() { - String[] classNames = followUnfollowButton.getAttribute("class").split(" "); - for (String className : classNames) { - if ("unfollow".equals(className)) { - return true; - } - } - - return false; + return followingContainer.isDisplayed(); } public ProfileUserProfilePage toggleFollowButton() { - this.followUnfollowButton.click(); + WebElement container = (isFollowed()) ? followingContainer : notFollowingContainer; + getDriver().findElementWithoutWaiting(container, By.tagName("button")).click(); + container.findElement(By.className("dropdown-menu")).findElement(By.tagName("a")).click(); + waitForNotificationSuccessMessage("Done"); return new ProfileUserProfilePage(this.getUsername()); } From 29f57a7db2e5366c9e23f109e3d0757af89c9e34 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 20 Mar 2024 16:16:22 +0100 Subject: [PATCH 24/24] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Sort by filter identifier by default --- ...mFiltersLiveDataConfigurationProvider.java | 16 +++++++++ .../NotificationCustomFiltersQueryHelper.java | 3 ++ .../test/ui/NotificationsSettingsIT.java | 33 ++++++++++--------- ...ficationsCustomFiltersPreferencesMacro.xml | 5 +-- .../XWiki/Notifications/Code/Translations.xml | 1 + 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java index ef65c2ef55e8..77225e0b1d24 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java @@ -159,6 +159,7 @@ public LiveDataConfiguration get() meta.setActions(List.of(deleteAction)); meta.setPropertyDescriptors(List.of( + getIDDescriptor(), getDisplayDescriptor(), getScopeDescriptor(), getLocationDescriptor(), @@ -172,6 +173,21 @@ public LiveDataConfiguration get() return input; } + private LiveDataPropertyDescriptor getIDDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + ID_FIELD)); + descriptor.setId(ID_FIELD); + descriptor.setType(STRING_TYPE); + // This is not visible we want to use that field only for sorting + descriptor.setVisible(false); + descriptor.setEditable(false); + descriptor.setSortable(true); + descriptor.setFilterable(false); + + return descriptor; + } + private LiveDataPropertyDescriptor getDisplayDescriptor() { LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java index 3da73ea489cf..0612fb155497 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -289,6 +289,9 @@ private String handleSortEntries(List sortEntries) thro case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> String.format("nfp.allEventTypes %s", sortOperator); + case NotificationCustomFiltersLiveDataConfigurationProvider.ID_FIELD -> + String.format("nfp.internalId %s", sortOperator); + default -> throw new LiveDataException("Unexpected sort value: " + sortEntry.getProperty()); }; clauses.add(clause); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java index fa1cadba0c88..0fffbb5e27c1 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-test/xwiki-platform-notifications-test-docker/src/test/it/org/xwiki/platform/notifications/test/ui/NotificationsSettingsIT.java @@ -103,7 +103,7 @@ void setup(TestUtils testUtils) @AfterEach void tearDown(TestUtils testUtils) { - testUtils.deletePage("XWiki", FIRST_USER_NAME); + //testUtils.deletePage("XWiki", FIRST_USER_NAME); } @Test @@ -675,7 +675,8 @@ void customFiltersAndLiveData(TestUtils testUtils, TestReference testReference) customNotificationFilterPreferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(2, customNotificationFilterPreferences.size()); - filterPreference = customNotificationFilterPreferences.get(1); + // Filters are ordered in descending order of creation + filterPreference = customNotificationFilterPreferences.get(0); assertEquals("Page only", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".SubSpace.SubPage", @@ -698,14 +699,6 @@ void customFiltersAndLiveData(TestUtils testUtils, TestReference testReference) filterPreference = customNotificationFilterPreferences.get(0); - assertEquals("Page and children", filterPreference.getScope()); - assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".WebHome", filterPreference.getLocation()); - assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); - assertEquals(List.of("Email"), filterPreference.getFormats()); - assertEquals(List.of("A page is modified"), filterPreference.getEventTypes()); - - filterPreference = customNotificationFilterPreferences.get(1); - assertEquals("Page only", filterPreference.getScope()); assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".SubSpace.SubPage", filterPreference.getLocation()); @@ -713,6 +706,14 @@ void customFiltersAndLiveData(TestUtils testUtils, TestReference testReference) assertEquals(List.of("Alert", "Email"), filterPreference.getFormats()); assertTrue(filterPreference.getEventTypes().isEmpty()); + filterPreference = customNotificationFilterPreferences.get(1); + + assertEquals("Page and children", filterPreference.getScope()); + assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".WebHome", filterPreference.getLocation()); + assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); + assertEquals(List.of("Email"), filterPreference.getFormats()); + assertEquals(List.of("A page is modified"), filterPreference.getEventTypes()); + // add a final filter: it will actually create 2 filters since we select several locations customNotificationFilterModal = notificationsUserProfilePage.clickAddCustomFilter(); customNotificationFilterModal.selectAction(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT); @@ -731,23 +732,23 @@ void customFiltersAndLiveData(TestUtils testUtils, TestReference testReference) customNotificationFilterPreferences = customPrefLiveData.getCustomNotificationFilterPreferences(); assertEquals(4, customNotificationFilterPreferences.size()); - filterPreference = customNotificationFilterPreferences.get(2); + filterPreference = customNotificationFilterPreferences.get(0); assertEquals("Page only", filterPreference.getScope()); - assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".Page1", filterPreference.getLocation()); + assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".Page2", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); assertEquals(List.of("Alert"), filterPreference.getFormats()); // core.events.delete before core.events.update - assertEquals(List.of("A page is deleted", "A page is modified"), filterPreference.getEventTypes()); + assertEquals(List.of("A page is deleted","A page is modified"), filterPreference.getEventTypes()); - filterPreference = customNotificationFilterPreferences.get(3); + filterPreference = customNotificationFilterPreferences.get(1); assertEquals("Page only", filterPreference.getScope()); - assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".Page2", filterPreference.getLocation()); + assertEquals(NotificationsSettingsIT.class.getSimpleName() + ".Page1", filterPreference.getLocation()); assertEquals(CustomNotificationFilterPreference.FilterAction.NOTIFY_EVENT, filterPreference.getFilterAction()); assertEquals(List.of("Alert"), filterPreference.getFormats()); // core.events.delete before core.events.update - assertEquals(List.of("A page is deleted","A page is modified"), filterPreference.getEventTypes()); + assertEquals(List.of("A page is deleted", "A page is modified"), filterPreference.getEventTypes()); // follow a user ProfileUserProfilePage userProfilePage = ProfileUserProfilePage.gotoPage(FIRST_USER_NAME); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index 4bad4fe6febf..99f1029890d8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -811,10 +811,11 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', #end $services.liveData.render({ 'id': 'notificationCustomFilterPreferencesLiveData', - 'properties': 'display,scope,location,filterType,eventTypes,notificationFormats,isEnabled,actions', + 'properties': 'filterPreferenceId,display,scope,location,filterType,eventTypes,notificationFormats,isEnabled,actions', 'source': 'notificationCustomFilters', 'sourceParameters': $escapetool.url($sourceParameters), - 'limit': 10 + 'limit': 10, + 'sort': 'filterPreferenceId:desc' }) </div> ###################################################### diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml index b1b77042746f..11d9220e0391 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/Translations.xml @@ -104,6 +104,7 @@ notifications.settings.filters.preferences.system.table.isEnabled=Is enabled? notifications.settings.filters.preferences.custom.title=Custom Filters notifications.settings.filters.preferences.custom.hint=Create filters to control the notifications you want to receive. ## Header definition for notification filter preferences +notifications.settings.filters.preferences.custom.table.filterPreferenceId=Identifier notifications.settings.filters.preferences.custom.table.scope=Scope notifications.settings.filters.preferences.custom.table.location=Location notifications.settings.filters.preferences.custom.table.filterType=Filter Action