001/* 002 * Copyright 2025 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.workspaces.activities.activitystream; 017 018import java.time.ZonedDateTime; 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.component.Component; 029import org.apache.avalon.framework.context.Context; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.components.ContextHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.commons.lang3.StringUtils; 038 039import org.ametys.core.user.CurrentUserProvider; 040import org.ametys.core.user.UserIdentity; 041import org.ametys.core.userpref.UserPreferencesException; 042import org.ametys.core.userpref.UserPreferencesManager; 043import org.ametys.core.util.DateUtils; 044import org.ametys.plugins.workspaces.project.ProjectManager; 045import org.ametys.plugins.workspaces.project.modules.WorkspaceModule; 046import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 047import org.ametys.plugins.workspaces.project.objects.Project; 048import org.ametys.web.WebHelper; 049 050/** 051 * Component to return a map for action to get the activities for activity stream 052 */ 053public class ActivityStreamServiceComponent implements Serviceable, Component, Contextualizable 054{ 055 /** The avalon role */ 056 public static final String ROLE = ActivityStreamServiceComponent.class.getName(); 057 058 private ActivityStreamClientInteraction _activityStream; 059 private ProjectManager _projectManager; 060 private UserPreferencesManager _userPrefManager; 061 private CurrentUserProvider _currentUserProvider; 062 private WorkspaceModuleExtensionPoint _moduleEP; 063 private Context _context; 064 065 066 public void service(ServiceManager manager) throws ServiceException 067 { 068 _activityStream = (ActivityStreamClientInteraction) manager.lookup(ActivityStreamClientInteraction.ROLE); 069 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 070 _userPrefManager = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE); 071 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 072 _moduleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 073 } 074 075 public void contextualize(Context context) throws ContextException 076 { 077 _context = context; 078 } 079 080 /** 081 * Get the activity stream of the current user. In project context, only the activities of the current project are returned, unless allActivities is true. 082 * @param limit The max number of results 083 * @param allActivities Set to true to force to get activities from all projects of the current user, even if we are in a project context 084 * @param markAsRead If true mark the activities as read 085 * @return The activity stream 086 * @throws UserPreferencesException If an error occurs while marking activities as read 087 */ 088 public Map<String, Object> getActivityStream(int limit, boolean allActivities, boolean markAsRead) throws UserPreferencesException 089 { 090 Request request = ContextHelper.getRequest(_context); 091 092 // The filtered modules 093 Set<String> activityTypes = _getFilteredActivityTypes(request); 094 095 // The filtered projects' categories 096 Set<String> categories = _getFilteredCategories(request); 097 098 // The filtered projects 099 Set<String> projects = _getFilteredProjects(request); 100 101 // Fultext search 102 String pattern = request.getParameter("pattern"); 103 104 // Dante range 105 ZonedDateTime fromDate = null; 106 String fromDateAsStr = request.getParameter("fromDate"); 107 if (StringUtils.isNotEmpty(fromDateAsStr)) 108 { 109 fromDate = DateUtils.parseZonedDateTime(fromDateAsStr); 110 } 111 ZonedDateTime untilDate = null; 112 String untilDateAsStr = request.getParameter("untilDate"); 113 if (StringUtils.isNotEmpty(untilDateAsStr)) 114 { 115 untilDate = DateUtils.parseZonedDateTime(untilDateAsStr); 116 } 117 118 String siteName = WebHelper.getSiteName(request); 119 120 List<String> currentProjectNames = _projectManager.getProjectsForSite(siteName); 121 Project currentProject = !currentProjectNames.isEmpty() ? _projectManager.getProject(currentProjectNames.get(0)) : null; 122 123 List<Map<String, Object>> activities; 124 if (currentProject == null || allActivities) 125 { 126 // Get activity stream of all current user's projects 127 activities = _activityStream.getActivitiesForCurrentUser(pattern, projects, categories, activityTypes, fromDate, untilDate, limit); 128 } 129 else 130 { 131 // Get activity stream of current project only 132 activities = _activityStream.getActivitiesForCurrentUser(Collections.singleton(currentProject), activityTypes, fromDate, untilDate, pattern, limit); 133 } 134 135 UserIdentity user = _currentUserProvider.getUser(); 136 if (markAsRead && user != null) 137 { 138 // Mark as read in user prefs 139 String userPrefContext = ActivityStreamClientInteraction.ACTIVITY_STREAM_USER_PREF_CONTEXT; 140 if (currentProject != null && !allActivities) 141 { 142 // if we are in a project, use its name as context 143 userPrefContext += "/" + currentProject.getName(); 144 } 145 _userPrefManager.setUserPreferences(user, userPrefContext, Map.of(), Map.of(ActivityStreamClientInteraction.ACTIVITY_STREAM_USER_PREF_LAST_UPDATE, DateUtils.dateToString(new Date()))); 146 } 147 148 Map<String, Object> result = new HashMap<>(); 149 // FIXME this should be rename activities 150 result.put("events", activities); 151 152 return result; 153 } 154 155 private Set<String> _getFilteredActivityTypes(Request request) 156 { 157 String[] moduleIds = request.getParameterValues("modules"); 158 if (moduleIds != null) 159 { 160 return Arrays.stream(moduleIds) 161 .filter(StringUtils::isNotEmpty) 162 .map(id -> _moduleEP.<WorkspaceModule>getModule(id)) 163 .map(WorkspaceModule::getAllowedEventTypes) 164 .flatMap(Set::stream) 165 .collect(Collectors.toSet()); 166 } 167 else 168 { 169 return Set.of(); 170 } 171 } 172 173 private Set<String> _getFilteredCategories(Request request) 174 { 175 String[] categoryIds = request.getParameterValues("categories"); 176 if (categoryIds != null) 177 { 178 return Arrays.stream(categoryIds) 179 .filter(StringUtils::isNotEmpty) 180 .collect(Collectors.toSet()); 181 } 182 else 183 { 184 return Set.of(); 185 } 186 } 187 188 private Set<String> _getFilteredProjects(Request request) 189 { 190 String[] projectIds = request.getParameterValues("projects"); 191 if (projectIds != null) 192 { 193 return Arrays.stream(projectIds) 194 .filter(StringUtils::isNotEmpty) 195 .collect(Collectors.toSet()); 196 } 197 else 198 { 199 return Set.of(); 200 } 201 } 202}