Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #2509: New notifications UI #2530

Merged
merged 10 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import edu.harvard.iq.dataverse.persistence.JpaEntity;
import edu.harvard.iq.dataverse.persistence.config.PostgresJsonConverter;
import org.apache.commons.lang3.StringUtils;

import javax.persistence.Column;
import javax.persistence.Convert;
Expand Down Expand Up @@ -54,6 +55,8 @@ public class UserNotification implements Serializable, JpaEntity<Long> {
@Convert(converter = PostgresJsonConverter.class)
private String parameters;

private String searchLabel;

// -------------------- GETTERS --------------------

public Long getId() {
Expand Down Expand Up @@ -104,6 +107,10 @@ public String getParameters() {
return parameters;
}

public String getSearchLabel() {
return searchLabel;
}

// -------------------- SETTERS --------------------

public void setId(Long id) {
Expand Down Expand Up @@ -145,4 +152,8 @@ public void setAdditionalMessage(String returnToAuthorReason) {
public void setParameters(String parameters) {
this.parameters = parameters;
}

public void setSearchLabel(String searchLabel) {
this.searchLabel = StringUtils.lowerCase(searchLabel);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package edu.harvard.iq.dataverse.persistence.user;

/**
* Query parameters used for user notifications.
*/
public class UserNotificationQuery {

private final static int DEFAULT_RESULT_LIMIT = 10;

private Long userId;

private String searchLabel;

private int offset;

private int resultLimit = DEFAULT_RESULT_LIMIT;

private boolean ascending;

// -------------------- CONSTRUCTORS --------------------

private UserNotificationQuery() {
}

// -------------------- GETTERS --------------------

public Long getUserId() {
return userId;
}

public String getSearchLabel() {
return searchLabel;
}

public int getOffset() {
return offset;
}

public int getResultLimit() {
return resultLimit;
}

public boolean isAscending() {
return ascending;
}

// -------------------- LOGIC --------------------

static public UserNotificationQuery newQuery() {
return new UserNotificationQuery();
}
Comment on lines +49 to +51
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are making this a builder kind of class then I think that we should block creating the object outside of it by adding private constructor.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point, added private constructor.


public UserNotificationQuery withUserId(Long userId) {
this.userId = userId;
return this;
}

public UserNotificationQuery withSearchLabel(String searchLabel) {
this.searchLabel = searchLabel;
return this;
}

public UserNotificationQuery withOffset(int offset) {
this.offset = offset;
return this;
}

public UserNotificationQuery withResultLimit(int resultLimit) {
this.resultLimit = resultLimit;
return this;
}

public UserNotificationQuery withAscending(boolean ascending) {
this.ascending = ascending;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package edu.harvard.iq.dataverse.persistence.user;

import java.util.List;

/**
* Query result including the total count of available results.
*/
public class UserNotificationQueryResult {

private final List<UserNotification> result;

private final Long totalCount;

// -------------------- CONSTRUCTORS --------------------

public UserNotificationQueryResult(List<UserNotification> result, Long totalCount) {
this.result = result;
this.totalCount = totalCount;
}

// -------------------- GETTERS --------------------

public List<UserNotification> getResult() {
return result;
}

public Long getTotalCount() {
return totalCount;
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package edu.harvard.iq.dataverse.persistence.user;


import com.google.common.collect.Lists;
import edu.harvard.iq.dataverse.persistence.JpaRepository;
import org.apache.commons.lang3.StringUtils;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
* @author xyang
*/
@Stateless
public class UserNotificationRepository extends JpaRepository<Long, UserNotification> {
private final static int DELETE_BATCH_SIZE = 100;

@PersistenceContext(unitName = "VDCNet-ejbPU")
private EntityManager em;
Expand All @@ -25,6 +34,39 @@ public UserNotificationRepository() {

// -------------------- LOGIC --------------------

public UserNotificationQueryResult query(UserNotificationQuery queryParam) {

CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<UserNotification> query = criteriaBuilder.createQuery(UserNotification.class);
Root<UserNotification> root = query.from(UserNotification.class);

List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(queryParam.getSearchLabel())) {
predicates.add(criteriaBuilder.like(root.get("searchLabel"), "%" + queryParam.getSearchLabel().toLowerCase() + "%"));
}

if (queryParam.getUserId() != null) {
predicates.add(criteriaBuilder.equal(root.get("user").get("id"), queryParam.getUserId()));
}

query.select(root)
.where(predicates.toArray(new Predicate[]{}))
.orderBy(queryParam.isAscending() ? criteriaBuilder.asc(root.get("sendDate")) : criteriaBuilder.desc(root.get("sendDate")));

List<UserNotification> resultList = em.createQuery(query)
.setFirstResult(queryParam.getOffset())
.setMaxResults(queryParam.getResultLimit())
.getResultList();

CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
countQuery.select(criteriaBuilder.count(root))
.where(predicates.toArray(new Predicate[]{}));

Long totalCount = em.createQuery(countQuery).getSingleResult();

return new UserNotificationQueryResult(resultList, totalCount);
}

public List<UserNotification> findByUser(Long userId) {
return em.createQuery("select un from UserNotification un where un.user.id =:userId order by un.sendDate desc", UserNotification.class)
.setParameter("userId", userId)
Expand All @@ -41,6 +83,21 @@ public int updateRequestor(Long oldId, Long newId) {
.executeUpdate();
}

public int deleteByIds(Set<Long> ids) {
return Lists.partition(Lists.newArrayList(ids), DELETE_BATCH_SIZE).stream()
.mapToInt(idBatch ->
em.createQuery("delete from UserNotification where id in :ids", UserNotification.class)
.setParameter("ids", idBatch)
.executeUpdate())
.sum();
}

public int deleteByUser(Long userId) {
return em.createQuery("delete from UserNotification where user.id = :userId", UserNotification.class)
.setParameter("userId", userId)
.executeUpdate();
}

public Long getUnreadNotificationCountByUser(Long userId) {
return em.createQuery("select count(un) from UserNotification as un where un.user.id = :userId and un.readNotification = :readNotification", Long.class)
.setParameter("userId", userId)
Expand All @@ -65,4 +122,5 @@ public UserNotification findLastSubmitNotificationByObjectId(long datasetId) {
.getResultList();
return notifications.isEmpty() ? null : notifications.get(0);
}

}
12 changes: 11 additions & 1 deletion dataverse-persistence/src/main/resources/Bundle_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,17 @@ notification.checksumfail=One or more files in your upload failed checksum valid
notification.mail.import.filesystem=Dataset {2} ({0}/dataset.xhtml?persistentId={1}) has been successfully uploaded and verified.
notification.import.filesystem=Dataset <a href="/dataset.xhtml?persistentId={0}" title="{1}">{1}</a> has been successfully uploaded and verified.
notification.import.checksum=<a href="/dataset.xhtml?persistentId={0}" title="{1}">{1}</a>, dataset had file checksums added via a batch job.
removeNotification=Remove Notification
notification.table.column.detail=Notification details
notification.table.column.sendDate=Created
notification.table.recordsPerPage.title=Records per page
notification.table.empty.message=No notifications found
notification.table.searchTerm.watermark=Filter notifications\u2026
notification.deleteSelected.btn=Delete selected
notification.deleteSelected=Delete notifications
notification.deleteSelected.info=The notification(s) will be deleted after you click on the Delete button.
notification.numSelected={0} {0, choice, 0#notifications are|1#notification is|2#notifications are} currently selected.
notification.selectAll=Select all {0} notifications
notification.clearSelection=Clear selection.
groupAndRoles.manageTips=Here is where you can access and manage all the groups you belong to, and the roles you have been assigned.
user.message.signup.label=Create Account
user.message.signup.tip=Why have a Dataverse account? To create your own dataverse and customize it, add datasets, or request access to restricted files.
Expand Down
12 changes: 11 additions & 1 deletion dataverse-persistence/src/main/resources/Bundle_pl.properties
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,17 @@ notification.checksumfail=Co najmniej jeden z przes\u0142anych plik\u00F3w nie p
notification.mail.import.filesystem=Zbi\u00F3r danych {2} ({0}/dataset.xhtml?persistentId={1}) zosta\u0142 pomy\u015Blnie przes\u0142any i sprawdzony.
notification.import.filesystem=Zbi\u00F3r danych <a href="/dataset.xhtml?persistentId={0}" title="{1}">{1}</a> zosta\u0142 pomy\u015Blnie przes\u0142any i sprawdzony.
notification.import.checksum=Do plik\u00F3w w zbiorze danych <a href="/dataset.xhtml?persistentId={0}" title="{1}">{1}</a> dodano sumy kontrolne przez zadanie asynchroniczne.
removeNotification=Usu\u0144 powiadomienie
notification.table.column.detail=Szczeg\u00F3\u0142y powiadomienia
notification.table.column.sendDate=Stworzony
notification.table.recordsPerPage.title=Rekord\u00F3w na stron\u0119
notification.table.empty.message=Nie znaleziono \u017Cadnych powiadomie\u0144
notification.table.searchTerm.watermark=Przefiltruj powiadomienia\u2026
notification.deleteSelected.btn=Usu\u0144 wybrane
notification.deleteSelected=Usu\u0144 powiadomienia
notification.deleteSelected.info=Powiadomienia zostan\u0105 usuni\u0119ty po naci\u015Bni\u0119ciu przycisku "Usu\u0144".
notification.numSelected=Obecnie wybrano {0} {0,choice,0#powiadomie\u0144|1#powiadomienie|2#powiadomienia|3#powiadomienia|4#powiadomienia|5#powiadomie\u0144}
notification.selectAll=Wybierz wszystkie powiadomienia
notification.clearSelection=Wyczy\u015B\u0107 wyb\u00F3r
groupAndRoles.manageTips=Tu masz dost\u0119p do wszystkich grup, do kt\u00F3rych nale\u017Cysz i do przydzielonych Ci r\u00F3l, oraz mo\u017Cliwo\u015B\u0107 zarz\u0105dzania nimi.
user.message.signup.label=Za\u0142\u00F3\u017C konto
user.message.signup.tip=Maj\u0105c konto w repozytorium mo\u017Cesz za\u0142o\u017Cy\u0107 w\u0142asn\u0105 kolekcj\u0119, dodawa\u0107 zbiory danych i prosi\u0107 o dost\u0119p do zastrze\u017Conych plik\u00F3w.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE usernotification ADD COLUMN searchLabel TEXT;

CREATE INDEX index_usernotification_searchLabel on usernotification (searchLabel);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.junit.Test;

import javax.inject.Inject;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -85,4 +84,52 @@ public void findLastSubmitNotificationForDataset() {
assertThat(notification).extracting(UserNotification::getId, UserNotification::getType)
.containsExactly(3L, NotificationType.SUBMITTEDDS);
}

@Test
public void query() {
// given
UserNotificationQuery query = UserNotificationQuery.newQuery();

// when
UserNotificationQueryResult result = userNotificationRepository.query(query);

// then
assertThat(result.getResult()).hasSize(5);
assertThat(result.getTotalCount()).isEqualTo(5);
}

@Test
public void query_paging() {
// given & when
UserNotificationQueryResult page1 = userNotificationRepository.query(UserNotificationQuery.newQuery()
.withUserId(2L)
.withResultLimit(2));

// then
assertThat(page1.getResult()).extracting(UserNotification::getId).containsExactly(3L, 2L);
assertThat(page1.getTotalCount()).isEqualTo(3);

// given & when
UserNotificationQueryResult page2 = userNotificationRepository.query(UserNotificationQuery.newQuery()
.withUserId(2L)
.withOffset(2)
.withResultLimit(2));

// then
assertThat(page2.getResult()).extracting(UserNotification::getId).containsExactly(1L);
assertThat(page2.getTotalCount()).isEqualTo(3);
}

@Test
public void query_filtering() {
// given
UserNotificationQuery query = UserNotificationQuery.newQuery().withSearchLabel("Monkeys");

// when
UserNotificationQueryResult result = userNotificationRepository.query(query);

// then
assertThat(result.getResult()).extracting(UserNotification::getId).containsExactly(4L, 3L);
assertThat(result.getTotalCount()).isEqualTo(2);
}
}
16 changes: 8 additions & 8 deletions dataverse-persistence/src/test/resources/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,14 @@ VALUES ('{"email": "[email protected]"}', 'SEND_NEWSLETTER_EMAIL', 1);

-------------------- USER NOTIFICATIONS ---------------------

INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate)
VALUES (1, 2, 'CREATEACC', NULL, true, true, '2020-09-24 13:01:00');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate)
VALUES (2, 2, 'CREATEDV', 19, false, true, '2020-09-25 14:00:00');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters)
VALUES (3, 2, 'SUBMITTEDDS', 44, false, false, '2020-09-26 10:00:00', '{"message":"message to curator", "requestorId":"4"}');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters)
VALUES (4, 4, 'RETURNEDDS', 44, false, false, '2020-09-26 10:10:00', '{"message":"send back to author", "requestorId":"2"}');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, searchlabel)
VALUES (1, 2, 'CREATEACC', NULL, true, true, '2020-09-24 13:01:00', '[email protected]');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, searchlabel)
VALUES (2, 2, 'CREATEDV', 19, false, true, '2020-09-25 14:00:00', 'datauniverse');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters, searchlabel)
VALUES (3, 2, 'SUBMITTEDDS', 44, false, false, '2020-09-26 10:00:00', '{"message":"message to curator", "requestorId":"4"}', 'monkeys dataset');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters, searchlabel)
VALUES (4, 4, 'RETURNEDDS', 44, false, false, '2020-09-26 10:10:00', '{"message":"send back to author", "requestorId":"2"}', 'monkeys dataset');
INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters)
VALUES (5, 4, 'RETURNEDDS', 44, false, false, '2020-09-26 10:10:00', '{"message":"send back to author", "requestorId":"3"}');
-------------------- SUGGESTIONS ---------------------
Expand Down
Loading
Loading