Merge branch '112-ping-stats' into 'dev'

Resolve "Сводка по ping активностей"

Closes #112

See merge request romanov73/ng-tracker!109
This commit is contained in:
Anton Romanov 2019-06-05 20:04:44 +00:00
commit 1ccec83390
31 changed files with 470 additions and 60 deletions

View File

@ -6,6 +6,7 @@ import org.hibernate.validator.constraints.NotBlank;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.EventSource; import ru.ulstu.core.model.EventSource;
import ru.ulstu.core.model.UserActivity;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.Paper;
import ru.ulstu.timeline.model.Event; import ru.ulstu.timeline.model.Event;
@ -13,6 +14,7 @@ import ru.ulstu.user.model.User;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
@ -28,10 +30,13 @@ import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@Entity @Entity
@Table(name = "conference") @Table(name = "conference")
public class Conference extends BaseEntity implements EventSource { @DiscriminatorValue("CONFERENCE")
public class Conference extends BaseEntity implements UserActivity, EventSource {
@NotBlank @NotBlank
private String title; private String title;
@ -163,4 +168,9 @@ public class Conference extends BaseEntity implements EventSource {
.filter(d -> d.getDate().after(new Date())) .filter(d -> d.getDate().after(new Date()))
.findFirst(); .findFirst();
} }
@Override
public Set<User> getActivityUsers() {
return getUsers().stream().map(ConferenceUser::getUser).collect(Collectors.toSet());
}
} }

View File

@ -243,10 +243,9 @@ public class ConferenceService extends BaseService {
} }
@Transactional @Transactional
public Ping ping(ConferenceDto conferenceDto) throws IOException { public void ping(ConferenceDto conferenceDto) throws IOException {
Ping ping = pingService.addPing(findOne(conferenceDto.getId())); pingService.addPing(findOne(conferenceDto.getId()));
conferenceRepository.updatePingConference(conferenceDto.getId()); conferenceRepository.updatePingConference(conferenceDto.getId());
return ping;
} }
private Conference findOne(Integer conferenceId) { private Conference findOne(Integer conferenceId) {

View File

@ -4,6 +4,7 @@ import ru.ulstu.user.model.User;
import java.util.Set; import java.util.Set;
public interface UserContainer { public interface UserActivity {
Set<User> getUsers(); String getTitle();
Set<User> getActivityUsers();
} }

View File

@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.grant.model.Grant; import ru.ulstu.grant.model.Grant;
import ru.ulstu.grant.model.GrantDto; import ru.ulstu.grant.model.GrantDto;
@ -123,4 +124,10 @@ public class GrantController {
public List<PaperDto> getAllPapers() { public List<PaperDto> getAllPapers() {
return grantService.getAllUncompletedPapers(); return grantService.getAllUncompletedPapers();
} }
@ResponseBody
@PostMapping(value = "/ping")
public void ping(@RequestParam("grantId") int grantId) throws IOException {
grantService.ping(grantId);
}
} }

View File

@ -5,7 +5,7 @@ import org.hibernate.annotations.FetchMode;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.EventSource; import ru.ulstu.core.model.EventSource;
import ru.ulstu.core.model.UserContainer; import ru.ulstu.core.model.UserActivity;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.file.model.FileData; import ru.ulstu.file.model.FileData;
import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.Paper;
@ -14,6 +14,7 @@ import ru.ulstu.timeline.model.Event;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
@ -37,7 +38,8 @@ import java.util.Set;
@Entity @Entity
@Table(name = "grants") @Table(name = "grants")
public class Grant extends BaseEntity implements UserContainer, EventSource { @DiscriminatorValue("GRANT")
public class Grant extends BaseEntity implements UserActivity, EventSource {
public enum GrantStatus { public enum GrantStatus {
APPLICATION("Заявка"), APPLICATION("Заявка"),
ON_COMPETITION("Отправлен на конкурс"), ON_COMPETITION("Отправлен на конкурс"),
@ -167,7 +169,7 @@ public class Grant extends BaseEntity implements UserContainer, EventSource {
} }
@Override @Override
public Set<User> getUsers() { public Set<User> getActivityUsers() {
return getAuthors(); return getAuthors();
} }

View File

@ -16,6 +16,7 @@ import ru.ulstu.name.BaseService;
import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.Paper;
import ru.ulstu.paper.model.PaperDto; import ru.ulstu.paper.model.PaperDto;
import ru.ulstu.paper.service.PaperService; import ru.ulstu.paper.service.PaperService;
import ru.ulstu.ping.service.PingService;
import ru.ulstu.project.model.Project; import ru.ulstu.project.model.Project;
import ru.ulstu.project.model.ProjectDto; import ru.ulstu.project.model.ProjectDto;
import ru.ulstu.project.service.ProjectService; import ru.ulstu.project.service.ProjectService;
@ -51,6 +52,7 @@ public class GrantService extends BaseService {
private final EventService eventService; private final EventService eventService;
private final GrantNotificationService grantNotificationService; private final GrantNotificationService grantNotificationService;
private final KiasService kiasService; private final KiasService kiasService;
private final PingService pingService;
public GrantService(GrantRepository grantRepository, public GrantService(GrantRepository grantRepository,
FileService fileService, FileService fileService,
@ -60,7 +62,8 @@ public class GrantService extends BaseService {
PaperService paperService, PaperService paperService,
EventService eventService, EventService eventService,
GrantNotificationService grantNotificationService, GrantNotificationService grantNotificationService,
KiasService kiasService) { KiasService kiasService,
PingService pingService) {
this.grantRepository = grantRepository; this.grantRepository = grantRepository;
this.kiasService = kiasService; this.kiasService = kiasService;
this.baseRepository = grantRepository; this.baseRepository = grantRepository;
@ -71,6 +74,7 @@ public class GrantService extends BaseService {
this.paperService = paperService; this.paperService = paperService;
this.eventService = eventService; this.eventService = eventService;
this.grantNotificationService = grantNotificationService; this.grantNotificationService = grantNotificationService;
this.pingService = pingService;
} }
public List<Grant> findAll() { public List<Grant> findAll() {
@ -334,6 +338,15 @@ public class GrantService extends BaseService {
return grantRepository.findAllActive(); return grantRepository.findAllActive();
} }
public Grant findById(Integer id) {
return grantRepository.findOne(id);
}
@Transactional
public void ping(int grantId) throws IOException {
pingService.addPing(findById(grantId));
}
public Grant findGrantById(Integer grantId) { public Grant findGrantById(Integer grantId) {
return grantRepository.findOne(grantId); return grantRepository.findOne(grantId);
} }

View File

@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.configuration.Constants; import ru.ulstu.configuration.Constants;
import ru.ulstu.core.model.response.Response; import ru.ulstu.core.model.response.Response;
@ -72,4 +73,9 @@ public class PaperRestController {
public Response<String> getFormattedReference(@RequestBody @Valid ReferenceDto referenceDto) { public Response<String> getFormattedReference(@RequestBody @Valid ReferenceDto referenceDto) {
return new Response<>(paperService.getFormattedReference(referenceDto)); return new Response<>(paperService.getFormattedReference(referenceDto));
} }
@PostMapping(value = "/ping")
public void ping(@RequestParam("paperId") int paperId) throws IOException {
paperService.ping(paperId);
}
} }

View File

@ -6,7 +6,7 @@ import org.hibernate.validator.constraints.NotBlank;
import ru.ulstu.conference.model.Conference; import ru.ulstu.conference.model.Conference;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.EventSource; import ru.ulstu.core.model.EventSource;
import ru.ulstu.core.model.UserContainer; import ru.ulstu.core.model.UserActivity;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.file.model.FileData; import ru.ulstu.file.model.FileData;
import ru.ulstu.grant.model.Grant; import ru.ulstu.grant.model.Grant;
@ -15,6 +15,7 @@ import ru.ulstu.user.model.User;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
@ -35,7 +36,8 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
@Entity @Entity
public class Paper extends BaseEntity implements UserContainer, EventSource { @DiscriminatorValue("PAPER")
public class Paper extends BaseEntity implements UserActivity, EventSource {
public enum PaperStatus { public enum PaperStatus {
ATTENTION("Обратить внимание"), ATTENTION("Обратить внимание"),
ON_PREPARATION("На подготовке"), ON_PREPARATION("На подготовке"),
@ -260,7 +262,7 @@ public class Paper extends BaseEntity implements UserContainer, EventSource {
} }
@Override @Override
public Set<User> getUsers() { public Set<User> getActivityUsers() {
return getAuthors(); return getAuthors();
} }

View File

@ -15,6 +15,7 @@ import ru.ulstu.paper.model.Reference;
import ru.ulstu.paper.model.ReferenceDto; import ru.ulstu.paper.model.ReferenceDto;
import ru.ulstu.paper.repository.PaperRepository; import ru.ulstu.paper.repository.PaperRepository;
import ru.ulstu.paper.repository.ReferenceRepository; import ru.ulstu.paper.repository.ReferenceRepository;
import ru.ulstu.ping.service.PingService;
import ru.ulstu.timeline.service.EventService; import ru.ulstu.timeline.service.EventService;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import ru.ulstu.user.service.UserService; import ru.ulstu.user.service.UserService;
@ -54,6 +55,7 @@ public class PaperService {
private final FileService fileService; private final FileService fileService;
private final EventService eventService; private final EventService eventService;
private final ReferenceRepository referenceRepository; private final ReferenceRepository referenceRepository;
private final PingService pingService;
public PaperService(PaperRepository paperRepository, public PaperService(PaperRepository paperRepository,
ReferenceRepository referenceRepository, ReferenceRepository referenceRepository,
@ -61,7 +63,8 @@ public class PaperService {
PaperNotificationService paperNotificationService, PaperNotificationService paperNotificationService,
UserService userService, UserService userService,
DeadlineService deadlineService, DeadlineService deadlineService,
EventService eventService) { EventService eventService,
PingService pingService) {
this.paperRepository = paperRepository; this.paperRepository = paperRepository;
this.referenceRepository = referenceRepository; this.referenceRepository = referenceRepository;
this.fileService = fileService; this.fileService = fileService;
@ -69,6 +72,7 @@ public class PaperService {
this.userService = userService; this.userService = userService;
this.deadlineService = deadlineService; this.deadlineService = deadlineService;
this.eventService = eventService; this.eventService = eventService;
this.pingService = pingService;
} }
public List<Paper> findAll() { public List<Paper> findAll() {
@ -386,4 +390,9 @@ public class PaperService {
autoCompleteData.setPublishers(referenceRepository.findDistinctPublishers()); autoCompleteData.setPublishers(referenceRepository.findDistinctPublishers());
return autoCompleteData; return autoCompleteData;
} }
@Transactional
public void ping(int paperId) throws IOException {
pingService.addPing(findPaperById(paperId));
}
} }

View File

@ -1,12 +1,20 @@
package ru.ulstu.ping.model; package ru.ulstu.ping.model;
import com.fasterxml.jackson.annotation.JsonProperty; import org.hibernate.annotations.Any;
import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.MetaValue;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import ru.ulstu.conference.model.Conference; import ru.ulstu.conference.model.Conference;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.UserActivity;
import ru.ulstu.grant.model.Grant;
import ru.ulstu.paper.model.Paper;
import ru.ulstu.project.model.Project;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.Table; import javax.persistence.Table;
@ -25,9 +33,24 @@ public class Ping extends BaseEntity {
@JoinColumn(name = "users_id") @JoinColumn(name = "users_id")
private User user; private User user;
@ManyToOne(optional = false) @Column(name = "activity_type", insertable = false, updatable = false)
@JoinColumn(name = "conference_id") private String activityType;
private Conference conference;
@Any(
metaColumn = @Column(name = "activity_type"),
fetch = FetchType.LAZY
)
@AnyMetaDef(
idType = "integer", metaType = "string",
metaValues = {
@MetaValue(targetEntity = Conference.class, value = "CONFERENCE"),
@MetaValue(targetEntity = Paper.class, value = "PAPER"),
@MetaValue(targetEntity = Project.class, value = "PROJECT"),
@MetaValue(targetEntity = Grant.class, value = "GRANT")
}
)
@JoinColumn(name = "activity_id")
private UserActivity activity;
public Ping() { public Ping() {
} }
@ -37,16 +60,6 @@ public class Ping extends BaseEntity {
this.user = user; this.user = user;
} }
public Ping(@JsonProperty("id") Integer id,
@JsonProperty("date") Date date,
@JsonProperty("user") User user,
@JsonProperty("conference") Conference conference) {
setId(id);
this.date = date;
this.user = user;
this.conference = conference;
}
public Date getDate() { public Date getDate() {
return date; return date;
} }
@ -63,11 +76,11 @@ public class Ping extends BaseEntity {
this.user = user; this.user = user;
} }
public Conference getConference() { public UserActivity getActivity() {
return conference; return this.activity;
} }
public void setConference(Conference conference) { public void setActivity(UserActivity activity) {
this.conference = conference; this.activity = activity;
} }
} }

View File

@ -0,0 +1,35 @@
package ru.ulstu.ping.model;
import ru.ulstu.user.model.User;
import java.util.ArrayList;
import java.util.List;
public class PingInfo {
private User user;
private List<Ping> pings = new ArrayList<>();
public PingInfo(User user) {
this.user = user;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<Ping> getPings() {
return pings;
}
public void setPings(List<Ping> pings) {
this.pings = pings;
}
public void addPing(Ping ping) {
this.pings.add(ping);
}
}

View File

@ -6,8 +6,17 @@ import org.springframework.data.repository.query.Param;
import ru.ulstu.conference.model.Conference; import ru.ulstu.conference.model.Conference;
import ru.ulstu.ping.model.Ping; import ru.ulstu.ping.model.Ping;
import java.util.Date;
import java.util.List;
public interface PingRepository extends JpaRepository<Ping, Integer> { public interface PingRepository extends JpaRepository<Ping, Integer> {
@Query("SELECT count(*) FROM Ping p WHERE (DAY(p.date) = :day) AND (MONTH(p.date) = :month) AND (YEAR(p.date) = :year) AND (p.conference = :conference)") @Query("SELECT count(*) FROM Ping p WHERE (DAY(p.date) = :day) AND (MONTH(p.date) = :month) AND (YEAR(p.date) = :year) AND (p.activityType = 'conference') AND (p.activity = :conference)")
long countByConferenceAndDate(@Param("conference") Conference conference, @Param("day") Integer day, @Param("month") Integer month, @Param("year") Integer year); long countByConferenceAndDate(@Param("conference") Conference conference, @Param("day") Integer day, @Param("month") Integer month, @Param("year") Integer year);
@Query("SELECT p FROM Ping p WHERE (:activity = '' OR p.activityType = :activity)")
List<Ping> getPings(@Param("activity") String activity);
@Query("SELECT p FROM Ping p WHERE (:dateFrom < date)")
List<Ping> findByDate(@Param("dateFrom") Date dateFrom);
} }

View File

@ -0,0 +1,58 @@
package ru.ulstu.ping.service;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import ru.ulstu.core.model.UserActivity;
import ru.ulstu.ping.model.Ping;
import ru.ulstu.ping.model.PingInfo;
import ru.ulstu.ping.repository.PingRepository;
import ru.ulstu.user.model.User;
import ru.ulstu.user.service.MailService;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Service
public class PingScheduler {
private final Logger log = LoggerFactory.getLogger(PingScheduler.class);
private final PingRepository pingRepository;
private final MailService mailService;
private final static String PING_MAIL_SUBJECT = "Ping статистика";
public PingScheduler(PingRepository pingRepository, MailService mailService) {
this.pingRepository = pingRepository;
this.mailService = mailService;
}
@Scheduled(cron = "0 0 * * 1 ?")
public void sendPingsInfo() {
log.debug("Scheduler.sendPingsInfo started");
List<PingInfo> pingInfos = new ArrayList<>();
for (Ping ping : pingRepository.findByDate(java.sql.Date.valueOf(LocalDate.now().minusWeeks(1)))) {
UserActivity pingActivity = ping.getActivity();
Set<User> users = pingActivity.getActivityUsers();
for (User user : users) {
PingInfo userPing = pingInfos.stream().filter(u -> u.getUser() == user).findFirst().orElse(null);
if (userPing == null) {
userPing = new PingInfo(user);
pingInfos.add(userPing);
}
userPing.addPing(ping);
}
}
for (PingInfo pingInfo : pingInfos) {
mailService.sendEmailFromTemplate(ImmutableMap.of("pings", pingInfo.getPings()),
pingInfo.getUser(), "pingsInfoWeekEmail", PING_MAIL_SUBJECT);
}
}
}

View File

@ -3,6 +3,7 @@ package ru.ulstu.ping.service;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.conference.model.Conference; import ru.ulstu.conference.model.Conference;
import ru.ulstu.core.model.UserActivity;
import ru.ulstu.ping.model.Ping; import ru.ulstu.ping.model.Ping;
import ru.ulstu.ping.repository.PingRepository; import ru.ulstu.ping.repository.PingRepository;
import ru.ulstu.user.service.UserService; import ru.ulstu.user.service.UserService;
@ -10,22 +11,26 @@ import ru.ulstu.user.service.UserService;
import java.io.IOException; import java.io.IOException;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List;
@Service @Service
public class PingService { public class PingService {
private final PingRepository pingRepository; private final PingRepository pingRepository;
private final UserService userService; private final UserService userService;
private final PingScheduler pingScheduler;
public PingService(PingRepository pingRepository, public PingService(PingRepository pingRepository,
UserService userService) { UserService userService,
PingScheduler pingScheduler) {
this.pingRepository = pingRepository; this.pingRepository = pingRepository;
this.userService = userService; this.userService = userService;
this.pingScheduler = pingScheduler;
} }
@Transactional @Transactional
public Ping addPing(Conference conference) throws IOException { public Ping addPing(UserActivity activity) throws IOException {
Ping newPing = new Ping(new Date(), userService.getCurrentUser()); Ping newPing = new Ping(new Date(), userService.getCurrentUser());
newPing.setConference(conference); newPing.setActivity(activity);
return pingRepository.save(newPing); return pingRepository.save(newPing);
} }
@ -33,4 +38,8 @@ public class PingService {
return Math.toIntExact(pingRepository.countByConferenceAndDate(conference, calendar.get(Calendar.DAY_OF_MONTH), return Math.toIntExact(pingRepository.countByConferenceAndDate(conference, calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR))); calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR)));
} }
public List<Ping> getPings(String activity) {
return pingRepository.getPings(activity);
}
} }

View File

@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.grant.model.GrantDto; import ru.ulstu.grant.model.GrantDto;
import ru.ulstu.project.model.Project; import ru.ulstu.project.model.Project;
@ -117,4 +118,10 @@ public class ProjectController {
.filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription()))
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
@ResponseBody
@PostMapping(value = "/ping")
public void ping(@RequestParam("projectId") int projectId) throws IOException {
projectService.ping(projectId);
}
} }

View File

@ -5,7 +5,7 @@ import org.hibernate.annotations.FetchMode;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.EventSource; import ru.ulstu.core.model.EventSource;
import ru.ulstu.core.model.UserContainer; import ru.ulstu.core.model.UserActivity;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.file.model.FileData; import ru.ulstu.file.model.FileData;
import ru.ulstu.grant.model.Grant; import ru.ulstu.grant.model.Grant;
@ -13,6 +13,7 @@ import ru.ulstu.timeline.model.Event;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
@ -30,7 +31,8 @@ import java.util.List;
import java.util.Set; import java.util.Set;
@Entity @Entity
public class Project extends BaseEntity implements UserContainer, EventSource { @DiscriminatorValue("PROJECT")
public class Project extends BaseEntity implements UserActivity, EventSource {
public enum ProjectStatus { public enum ProjectStatus {
TECHNICAL_TASK("Техническое задание"), TECHNICAL_TASK("Техническое задание"),
@ -172,10 +174,13 @@ public class Project extends BaseEntity implements UserContainer, EventSource {
this.executors = executors; this.executors = executors;
} }
@Override
public Set<User> getUsers() { public Set<User> getUsers() {
Set<User> users = new HashSet<User>(getExecutors()); return new HashSet<>(getExecutors());
return users; }
@Override
public Set<User> getActivityUsers() {
return new HashSet<>();
} }
public List<Grant> getGrants() { public List<Grant> getGrants() {

View File

@ -8,6 +8,7 @@ import ru.ulstu.file.model.FileDataDto;
import ru.ulstu.file.service.FileService; import ru.ulstu.file.service.FileService;
import ru.ulstu.grant.model.GrantDto; import ru.ulstu.grant.model.GrantDto;
import ru.ulstu.grant.repository.GrantRepository; import ru.ulstu.grant.repository.GrantRepository;
import ru.ulstu.ping.service.PingService;
import ru.ulstu.project.model.Project; import ru.ulstu.project.model.Project;
import ru.ulstu.project.model.ProjectDto; import ru.ulstu.project.model.ProjectDto;
import ru.ulstu.project.repository.ProjectRepository; import ru.ulstu.project.repository.ProjectRepository;
@ -35,19 +36,22 @@ public class ProjectService {
private final FileService fileService; private final FileService fileService;
private final EventService eventService; private final EventService eventService;
private final UserService userService; private final UserService userService;
private final PingService pingService;
public ProjectService(ProjectRepository projectRepository, public ProjectService(ProjectRepository projectRepository,
DeadlineService deadlineService, DeadlineService deadlineService,
GrantRepository grantRepository, GrantRepository grantRepository,
FileService fileService, FileService fileService,
EventService eventService, EventService eventService,
UserService userService) { UserService userService,
PingService pingService) {
this.projectRepository = projectRepository; this.projectRepository = projectRepository;
this.deadlineService = deadlineService; this.deadlineService = deadlineService;
this.grantRepository = grantRepository; this.grantRepository = grantRepository;
this.fileService = fileService; this.fileService = fileService;
this.eventService = eventService; this.eventService = eventService;
this.userService = userService; this.userService = userService;
this.pingService = pingService;
} }
public List<Project> findAll() { public List<Project> findAll() {
@ -143,6 +147,11 @@ public class ProjectService {
return users; return users;
} }
@Transactional
public void ping(int projectId) throws IOException {
pingService.addPing(findById(projectId));
}
public List<GrantDto> getAllGrants() { public List<GrantDto> getAllGrants() {
List<GrantDto> grants = convert(grantRepository.findAll(), GrantDto::new); List<GrantDto> grants = convert(grantRepository.findAll(), GrantDto::new);
return grants; return grants;

View File

@ -1,21 +1,21 @@
package ru.ulstu.strategy.api; package ru.ulstu.strategy.api;
import ru.ulstu.core.model.UserContainer; import ru.ulstu.core.model.UserActivity;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public abstract class EntityCreateStrategy<T extends UserContainer> { public abstract class EntityCreateStrategy<T extends UserActivity> {
protected abstract List<T> getActiveEntities(); protected abstract List<T> getActiveEntities();
protected abstract void createEntity(User user); protected abstract void createEntity(User user);
protected void createDefaultEntityIfNeed(List<User> allUsers, List<? extends UserContainer> entities) { protected void createDefaultEntityIfNeed(List<User> allUsers, List<? extends UserActivity> entities) {
allUsers.forEach(user -> { allUsers.forEach(user -> {
if (entities if (entities
.stream() .stream()
.filter(entity -> entity.getUsers().contains(user)) .filter(entity -> entity.getActivityUsers().contains(user))
.collect(Collectors.toSet()).isEmpty()) { .collect(Collectors.toSet()).isEmpty()) {
createEntity(user); createEntity(user);
} }

View File

@ -169,4 +169,10 @@ public class UserController extends OdinController<UserListDto, UserDto> {
public void inviteUser(@RequestParam("email") String email) { public void inviteUser(@RequestParam("email") String email) {
userService.inviteUser(email); userService.inviteUser(email);
} }
@GetMapping("/activities/pings")
public Response<Map<String, Integer>> getActivitesStats(@RequestParam(value = "userId", required = false) Integer userId,
@RequestParam(value = "activity", required = false) String activity) {
return new Response<>(userService.getActivitiesPings(userId, activity));
}
} }

View File

@ -1,10 +1,12 @@
package ru.ulstu.user.controller; package ru.ulstu.user.controller;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import ru.ulstu.configuration.Constants; import ru.ulstu.configuration.Constants;
@ -17,6 +19,8 @@ import ru.ulstu.user.service.UserSessionService;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
@Controller @Controller
@RequestMapping(value = "/users") @RequestMapping(value = "/users")
@ -53,4 +57,21 @@ public class UserMvcController extends OdinController<UserListDto, UserDto> {
public void getUsersDashboard(ModelMap modelMap) { public void getUsersDashboard(ModelMap modelMap) {
modelMap.addAllAttributes(userService.getUsersInfo()); modelMap.addAllAttributes(userService.getUsersInfo());
} }
@ModelAttribute("allUsers")
public List<User> getAllUsers() {
return userService.findAll();
}
@ModelAttribute("allActivities")
public Map<String, String> getAllActivites() {
return ImmutableMap.of("PAPER", "Статьи",
"GRANT", "Гранты",
"PROJECT", "Проекты",
"CONFERENCE", "Конференции");
}
@GetMapping("/pings")
public void getPings() {
}
} }

View File

@ -19,7 +19,13 @@ import ru.ulstu.configuration.ApplicationProperties;
import ru.ulstu.core.error.EntityIdIsNullException; import ru.ulstu.core.error.EntityIdIsNullException;
import ru.ulstu.core.jpa.OffsetablePageRequest; import ru.ulstu.core.jpa.OffsetablePageRequest;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.UserActivity;
import ru.ulstu.core.model.response.PageableItems; import ru.ulstu.core.model.response.PageableItems;
import ru.ulstu.grant.service.GrantService;
import ru.ulstu.paper.service.PaperService;
import ru.ulstu.ping.model.Ping;
import ru.ulstu.ping.service.PingService;
import ru.ulstu.project.service.ProjectService;
import ru.ulstu.user.error.UserActivationError; import ru.ulstu.user.error.UserActivationError;
import ru.ulstu.user.error.UserEmailExistsException; import ru.ulstu.user.error.UserEmailExistsException;
import ru.ulstu.user.error.UserIdExistsException; import ru.ulstu.user.error.UserIdExistsException;
@ -51,6 +57,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -73,6 +80,7 @@ public class UserService implements UserDetailsService {
private final TimetableService timetableService; private final TimetableService timetableService;
private final ConferenceService conferenceService; private final ConferenceService conferenceService;
private final UserSessionService userSessionService; private final UserSessionService userSessionService;
private final PingService pingService;
public UserService(UserRepository userRepository, public UserService(UserRepository userRepository,
PasswordEncoder passwordEncoder, PasswordEncoder passwordEncoder,
@ -80,6 +88,7 @@ public class UserService implements UserDetailsService {
UserMapper userMapper, UserMapper userMapper,
MailService mailService, MailService mailService,
ApplicationProperties applicationProperties, ApplicationProperties applicationProperties,
@Lazy PingService pingService,
@Lazy ConferenceService conferenceRepository, @Lazy ConferenceService conferenceRepository,
@Lazy UserSessionService userSessionService) throws ParseException { @Lazy UserSessionService userSessionService) throws ParseException {
this.userRepository = userRepository; this.userRepository = userRepository;
@ -91,6 +100,7 @@ public class UserService implements UserDetailsService {
this.conferenceService = conferenceRepository; this.conferenceService = conferenceRepository;
this.timetableService = new TimetableService(); this.timetableService = new TimetableService();
this.userSessionService = userSessionService; this.userSessionService = userSessionService;
this.pingService = pingService;
} }
private User getUserByEmail(String email) { private User getUserByEmail(String email) {
@ -389,4 +399,29 @@ public class UserService implements UserDetailsService {
} }
return ImmutableMap.of("users", usersInfoNow, "error", err); return ImmutableMap.of("users", usersInfoNow, "error", err);
} }
public Map<String, Integer> getActivitiesPings(Integer userId,
String activityName) {
User user = null;
if (userId != null) {
user = findById(userId);
}
Map<String, Integer> activitiesPings = new HashMap<>();
for (Ping ping : pingService.getPings(activityName)) {
UserActivity activity = ping.getActivity();
if (user != null && !activity.getActivityUsers().contains(user)) {
continue;
}
if (activitiesPings.containsKey(activity.getTitle())) {
activitiesPings.put(activity.getTitle(), activitiesPings.get(activity.getTitle()) + 1);
} else {
activitiesPings.put(activity.getTitle(), 1);
}
}
return activitiesPings;
}
} }

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="arefyev" id="20190525_000000-1">
<addColumn tableName="ping">
<column name="activity_type" type="varchar(20)"/>
<column name="activity_id" type="integer"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@ -52,4 +52,5 @@
<include file="db/changelog-20190529_000000-schema.xml"/> <include file="db/changelog-20190529_000000-schema.xml"/>
<include file="db/changelog-20190529_000001-schema.xml"/> <include file="db/changelog-20190529_000001-schema.xml"/>
<include file="db/changelog-20190601_000001-schema.xml"/> <include file="db/changelog-20190601_000001-schema.xml"/>
<include file="db/changelog-20190605_000000-schema.xml"/>
</databaseChangeLog> </databaseChangeLog>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Уведомление о создании статьи</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/>
</head>
<body>
<p>
Уважаемый(ая) <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span>
</p>
<p>
Вы были отмечены в следующих задачах:
</p>
<p th:each="ping : ${pings}" th:text="${ping.getActivity().getTitle() + ' ' + ping.getDate()}">
</p>
<p>
Regards,
<br/>
<em>NG-tracker.</em>
</p>
</body>
</html>

View File

@ -225,3 +225,19 @@ function getUrlVar(key) {
var result = new RegExp(key + "=([^&]*)", "i").exec(window.location.search); var result = new RegExp(key + "=([^&]*)", "i").exec(window.location.search);
return result && decodeURIComponent(result[1]) || ""; return result && decodeURIComponent(result[1]) || "";
} }
function sendPing(field, url) {
id = document.getElementById(field).value
$.ajax({
url:url + `?${field}=` + id,
contentType: "application/json; charset=utf-8",
method: "POST",
success: function() {
showFeedbackMessage("Ping был отправлен", MessageTypesEnum.SUCCESS)
},
error: function(errorData) {
showFeedbackMessage(errorData.responseJSON.error.message, MessageTypesEnum.WARNING)
}
})
}

View File

@ -125,3 +125,35 @@ function isEmailValid(email) {
re = /\S+@\S+\.\S+/; re = /\S+@\S+\.\S+/;
return re.test(email) return re.test(email)
} }
function drawChart() {
userId = $('#author :selected').val()
activity = $('#activity :selected').val()
$.ajax({
url:`/api/1.0/users/activities/pings?userId=${userId}&activity=${activity}`,
contentType: "application/json; charset=utf-8",
method: "GET",
success: function(response) {
if (response.data.length == 0) {
return;
}
array = [['Активности', 'Количество']]
Object.keys(response.data).forEach(function(key) {
console.table('Key : ' + key + ', Value : ' + response.data[key])
array.push([key, response.data[key]])
})
var data = google.visualization.arrayToDataTable(array);
var options = {
title: 'Активности',
is3D: true,
pieResidueSliceLabel: 'Остальное'
};
var chart = new google.visualization.PieChart(document.getElementById('air'));
chart.draw(data, options);
},
error: function(errorData) {
showFeedbackMessage(errorData.responseJSON.error.message, MessageTypesEnum.WARNING)
}
})
}

View File

@ -23,7 +23,7 @@
th:object="${grantDto}"> th:object="${grantDto}">
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<input type="hidden" name="id" th:field="*{id}"/> <input type="hidden" id="grantId" name="id" th:field="*{id}"/>
<div class="form-group"> <div class="form-group">
<label for="title">Название:</label> <label for="title">Название:</label>
<input class="form-control" id="title" type="text" <input class="form-control" id="title" type="text"
@ -199,6 +199,12 @@
</div> </div>
<input type = "hidden" th:field="*{project.id}"/> <input type = "hidden" th:field="*{project.id}"/>
</div> </div>
<div class="form-group">
<button id="pingButton" class="btn btn-primary text-uppercase"
type="button" onclick="sendPing('grantId', '/grants/ping')">
Ping авторам
</button>
</div>
--> -->
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>

View File

@ -43,7 +43,7 @@
<div class="tab-content" id="nav-tabContent"> <div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-main" role="tabpanel" <div class="tab-pane fade show active" id="nav-main" role="tabpanel"
aria-labelledby="nav-main-tab"> aria-labelledby="nav-main-tab">
<input type="hidden" name="id" th:field="*{id}"/> <input type="hidden" id="paperId" name="id" th:field="*{id}"/>
<div class="form-group"> <div class="form-group">
<label for="title">Название:</label> <label for="title">Название:</label>
<input class="form-control" id="title" type="text" <input class="form-control" id="title" type="text"
@ -330,7 +330,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<button id="pingButton" class="btn btn-primary text-uppercase" <button id="pingButton" class="btn btn-primary text-uppercase"
type="button"> type="button" onclick="sendPing('paperId', '/api/1.0/papers/ping')">
Ping авторам Ping авторам
</button> </button>
</div> </div>

View File

@ -24,7 +24,7 @@
th:object="${projectDto}"> th:object="${projectDto}">
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<input type="hidden" name="id" th:field="*{id}"/> <input type="hidden" id="projectId" name="id" th:field="*{id}"/>
<div class="form-group"> <div class="form-group">
<label for="title">Название:</label> <label for="title">Название:</label>
<input class="form-control" id="title" type="text" <input class="form-control" id="title" type="text"
@ -134,6 +134,12 @@
</div> </div>
</div> </div>
<div class="form-group">
<button id="pingButton" class="btn btn-primary text-uppercase"
type="button" onclick="sendPing('projectId', '/projects/ping')">
Ping авторам
</button>
</div>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -174,6 +180,22 @@
}); });
$('.selectpicker').selectpicker(); $('.selectpicker').selectpicker();
}); });
function sendPing() {
id = document.getElementById("projectId").value
$.ajax({
url:"/projects/ping?projectId=" + id,
contentType: "application/json; charset=utf-8",
method: "POST",
success: function() {
showFeedbackMessage("Ping был отправлен", MessageTypesEnum.SUCCESS)
},
error: function(errorData) {
showFeedbackMessage(errorData.responseJSON.error.message, MessageTypesEnum.WARNING)
}
})
}
/*]]>*/ /*]]>*/
function addNewFile(fileDto, listElement) { function addNewFile(fileDto, listElement) {
var fileNumber = $('.files-list div.row').length; var fileNumber = $('.files-list div.row').length;

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="default" xmlns:th="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/html">
<head>
<link rel="stylesheet" href="../css/grant.css"/>
<script src="https://www.google.com/jsapi"></script>
<script src="/js/users.js"></script>
<script src="/js/core.js"></script>
<script>
google.load('visualization', '1.0', {'packages':['corechart']});
google.setOnLoadCallback(drawChart);
</script>
</head>
<body>
<div class="container" layout:fragment="content">
<section id="ewrq">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Ping активности</h2>
</div>
<div id="air" style="width: 500px; height: 400px;" class="col-md-9 col-sm-12"></div>
<div class="col-md-3 col-sm-12">
<div class="filter">
<h5>Фильтр:</h5>
<select class="form-control" id="author"
onchange="drawChart();">
<option value="">Все авторы</option>
<option th:each="user: ${allUsers}" th:value="${user.id}"
th:text="${user.lastName}">lastName
</option>
</select>
<br/>
<select class="form-control" id="activity"
onchange="drawChart();">
<option value="">Все активности</option>
<option th:each="activity: ${allActivities}" th:value="${activity.key}"
th:text="${activity.value}">
</option>
</select>
</div>
</div>
</div>
</div>
</section>
</div>
</body>
</html>

View File

@ -276,14 +276,4 @@ public class ConferenceServiceTest {
assertTrue(conferenceService.isAttachedToConference(ID)); assertTrue(conferenceService.isAttachedToConference(ID));
} }
@Test
public void ping() throws IOException {
Ping ping = new Ping();
when(conferenceRepository.findOne(ID)).thenReturn(conferenceWithId);
when(pingService.addPing(conferenceWithId)).thenReturn(ping);
when(conferenceRepository.updatePingConference(ID)).thenReturn(INDEX);
assertEquals(ping, conferenceService.ping(conferenceDto));
}
} }