Closes #193: last updated date (db + search + ui)

This commit is contained in:
Benjamin Gamard 2018-03-12 14:15:00 +01:00
parent 9d8034e010
commit 5426be9fa0
16 changed files with 202 additions and 31 deletions

View File

@ -38,6 +38,7 @@ public class DocumentDao {
public String create(Document document, String userId) {
// Create the UUID
document.setId(UUID.randomUUID().toString());
document.setUpdateDate(new Date());
// Create the document
EntityManager em = ThreadLocalContext.get().getEntityManager();
@ -90,7 +91,7 @@ public class DocumentDao {
}
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, ");
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_UPDATEDATE_D, d.DOC_LANGUAGE_C, ");
sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null), ");
sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C), ");
sb.append(" u.USE_USERNAME_C ");
@ -122,6 +123,7 @@ public class DocumentDao {
documentDto.setCoverage((String) o[i++]);
documentDto.setRights((String) o[i++]);
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setUpdateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setLanguage((String) o[i++]);
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i++]).intValue());
@ -204,7 +206,7 @@ public class DocumentDao {
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, ");
sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null) c5, ");
sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C) c6, ");
sb.append(" rs2.RTP_ID_C c7, rs2.RTP_NAME_C ");
sb.append(" rs2.RTP_ID_C c7, rs2.RTP_NAME_C, d.DOC_UPDATEDATE_D c8 ");
sb.append(" from T_DOCUMENT d ");
sb.append(" left join (select rs.*, rs3.idDocument\n" +
"from T_ROUTE_STEP rs \n" +
@ -238,6 +240,14 @@ public class DocumentDao {
criteriaList.add("d.DOC_CREATEDATE_D <= :createDateMax");
parameterMap.put("createDateMax", criteria.getCreateDateMax());
}
if (criteria.getUpdateDateMin() != null) {
criteriaList.add("d.DOC_UPDATEDATE_D >= :updateDateMin");
parameterMap.put("updateDateMin", criteria.getUpdateDateMin());
}
if (criteria.getUpdateDateMax() != null) {
criteriaList.add("d.DOC_UPDATEDATE_D <= :updateDateMax");
parameterMap.put("updateDateMax", criteria.getUpdateDateMax());
}
if (criteria.getTagIdList() != null && !criteria.getTagIdList().isEmpty()) {
int index = 0;
List<String> tagCriteriaList = Lists.newArrayList();
@ -288,7 +298,8 @@ public class DocumentDao {
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i++]).intValue());
documentDto.setActiveRoute(o[i++] != null);
documentDto.setCurrentStepName((String) o[i]);
documentDto.setCurrentStepName((String) o[i++]);
documentDto.setUpdateTimestamp(((Timestamp) o[i]).getTime());
documentDtoList.add(documentDto);
}
@ -323,6 +334,7 @@ public class DocumentDao {
documentDb.setRights(document.getRights());
documentDb.setCreateDate(document.getCreateDate());
documentDb.setLanguage(document.getLanguage());
documentDb.setUpdateDate(new Date());
// Create audit log
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);

View File

@ -35,6 +35,16 @@ public class DocumentCriteria {
*/
private Date createDateMax;
/**
* Minimum update date.
*/
private Date updateDateMin;
/**
* Maximum update date.
*/
private Date updateDateMax;
/**
* Tag IDs.
*/
@ -136,6 +146,22 @@ public class DocumentCriteria {
return activeRoute;
}
public Date getUpdateDateMin() {
return updateDateMin;
}
public void setUpdateDateMin(Date updateDateMin) {
this.updateDateMin = updateDateMin;
}
public Date getUpdateDateMax() {
return updateDateMax;
}
public void setUpdateDateMax(Date updateDateMax) {
this.updateDateMax = updateDateMax;
}
public DocumentCriteria setActiveRoute(Boolean activeRoute) {
this.activeRoute = activeRoute;
return this;

View File

@ -71,6 +71,11 @@ public class DocumentDto {
*/
private Long createTimestamp;
/**
* Update date.
*/
private Long updateTimestamp;
/**
* Shared status.
*/
@ -236,6 +241,14 @@ public class DocumentDto {
return currentStepName;
}
public Long getUpdateTimestamp() {
return updateTimestamp;
}
public void setUpdateTimestamp(Long updateTimestamp) {
this.updateTimestamp = updateTimestamp;
}
public DocumentDto setCurrentStepName(String currentStepName) {
this.currentStepName = currentStepName;
return this;

View File

@ -1,13 +1,12 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.MoreObjects;
import java.util.Date;
/**
* Document entity.
@ -102,6 +101,12 @@ public class Document implements Loggable {
@Column(name = "DOC_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Creation date.
*/
@Column(name = "DOC_UPDATEDATE_D", nullable = false)
private Date updateDate;
/**
* Deletion date.
*/
@ -229,6 +234,14 @@ public class Document implements Loggable {
this.deleteDate = deleteDate;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)

View File

@ -1 +1 @@
db.version=17
db.version=18

View File

@ -0,0 +1,4 @@
alter table T_DOCUMENT add column DOC_UPDATEDATE_D datetime;
update T_DOCUMENT set DOC_UPDATEDATE_D = DOC_CREATEDATE_D;
alter table T_DOCUMENT alter column DOC_UPDATEDATE_D datetime not null;
update T_CONFIG set CFG_VALUE_C = '18' where CFG_ID_C = 'DB_VERSION';

View File

@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=17
db.version=18

View File

@ -76,6 +76,7 @@ public class DocumentResource extends BaseResource {
* @apiSuccess {String} title Title
* @apiSuccess {String} description Description
* @apiSuccess {Number} create_date Create date (timestamp)
* @apiSuccess {Number} update_date Update date (timestamp)
* @apiSuccess {String} language Language
* @apiSuccess {Boolean} shared True if the document is shared
* @apiSuccess {Number} file_count Number of files in this document
@ -142,6 +143,7 @@ public class DocumentResource extends BaseResource {
.add("title", documentDto.getTitle())
.add("description", JsonUtil.nullable(documentDto.getDescription()))
.add("create_date", documentDto.getCreateTimestamp())
.add("update_date", documentDto.getUpdateTimestamp())
.add("language", documentDto.getLanguage())
.add("shared", documentDto.getShared())
.add("file_count", documentDto.getFileCount());
@ -328,6 +330,7 @@ public class DocumentResource extends BaseResource {
* @apiSuccess {String} documents.title Title
* @apiSuccess {String} documents.description Description
* @apiSuccess {Number} documents.create_date Create date (timestamp)
* @apiSuccess {Number} documents.update_date Update date (timestamp)
* @apiSuccess {String} documents.language Language
* @apiSuccess {Boolean} documents.shared True if the document is shared
* @apiSuccess {Boolean} documents.active_route True if a route is active on this document
@ -394,6 +397,7 @@ public class DocumentResource extends BaseResource {
.add("title", documentDto.getTitle())
.add("description", JsonUtil.nullable(documentDto.getDescription()))
.add("create_date", documentDto.getCreateTimestamp())
.add("update_date", documentDto.getUpdateTimestamp())
.add("language", documentDto.getLanguage())
.add("shared", documentDto.getShared())
.add("active_route", documentDto.isActiveRoute())
@ -464,33 +468,58 @@ public class DocumentResource extends BaseResource {
break;
case "after":
case "before":
case "uafter":
case "ubefore":
// New date span criteria
try {
boolean isUpdated = params[0].startsWith("u");
DateTime date = formatter.parseDateTime(params[1]);
if (params[0].equals("before")) documentCriteria.setCreateDateMax(date.toDate());
if (params[0].endsWith("before")) {
if (isUpdated) documentCriteria.setUpdateDateMax(date.toDate());
else documentCriteria.setCreateDateMax(date.toDate());
} else {
if (isUpdated) documentCriteria.setUpdateDateMin(date.toDate());
else documentCriteria.setCreateDateMin(date.toDate());
}
} catch (IllegalArgumentException e) {
// Invalid date, returns no documents
if (params[0].equals("before")) documentCriteria.setCreateDateMax(new Date(0));
else documentCriteria.setCreateDateMin(new Date(Long.MAX_VALUE / 2));
documentCriteria.setCreateDateMin(new Date(0));
documentCriteria.setCreateDateMax(new Date(0));
}
break;
case "uat":
case "at":
// New specific date criteria
try {
boolean isUpdated = params[0].startsWith("u");
if (params[1].length() == 10) {
DateTime date = dayFormatter.parseDateTime(params[1]);
if (isUpdated) {
documentCriteria.setUpdateDateMin(date.toDate());
documentCriteria.setUpdateDateMax(date.plusDays(1).minusSeconds(1).toDate());
} else {
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusDays(1).minusSeconds(1).toDate());
}
} else if (params[1].length() == 7) {
DateTime date = monthFormatter.parseDateTime(params[1]);
if (isUpdated) {
documentCriteria.setUpdateDateMin(date.toDate());
documentCriteria.setUpdateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
} else {
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
}
} else if (params[1].length() == 4) {
DateTime date = yearFormatter.parseDateTime(params[1]);
if (isUpdated) {
documentCriteria.setUpdateDateMin(date.toDate());
documentCriteria.setUpdateDateMax(date.plusYears(1).minusSeconds(1).toDate());
} else {
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate());
}
}
} catch (IllegalArgumentException e) {
// Invalid date, returns no documents
documentCriteria.setCreateDateMin(new Date(0));

View File

@ -181,6 +181,12 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
if (!_.isUndefined($scope.advsearch.before_date)) {
search += 'before:' + $filter('date')($scope.advsearch.before_date, 'yyyy-MM-dd') + ' ';
}
if (!_.isUndefined($scope.advsearch.after_update_date)) {
search += 'uafter:' + $filter('date')($scope.advsearch.after_update_date, 'yyyy-MM-dd') + ' ';
}
if (!_.isUndefined($scope.advsearch.before_update_date)) {
search += 'ubefore:' + $filter('date')($scope.advsearch.before_update_date, 'yyyy-MM-dd') + ' ';
}
if (!_.isEmpty($scope.advsearch.tags)) {
search += _.reduce($scope.advsearch.tags, function(s, t) {
return s + 'tag:' + t.name + ' ';

View File

@ -45,8 +45,10 @@
"search_fulltext": "Fulltext search",
"search_creator": "Creator",
"search_language": "Language",
"search_before_date": "Before this date",
"search_after_date": "After this date",
"search_before_date": "Created before this date",
"search_after_date": "Created after this date",
"search_before_update_date": "Updated before this date",
"search_after_update_date": "Update after this date",
"search_tags": "Tags",
"search_shared": "Only shared documents",
"search_workflow": "Workflow assigned to me",
@ -82,6 +84,7 @@
"upgrade_quota": "To upgrade your quota, ask your administrator",
"quota": "{{ current | number: 0 }}MB ({{ percent | number: 1 }}%) used on {{ total | number: 0 }}MB",
"count": "{{ count }} document{{ count > 1 ? 's' : '' }} found",
"last_updated": "Last updated {{ date | timeAgo: dateFormat }}",
"view": {
"delete_comment_title": "Delete comment",
"delete_comment_message": "Do you really want to delete this comment?",

View File

@ -93,6 +93,36 @@
</div>
</div>
<div class="form-group">
<label class="control-label sr-only" for="inputSearchAfterUpdateDate">{{ 'document.search_after_update_date' | translate }}</label>
<div class="col-sm-12">
<input type="text" id="inputSearchAfterUpdateDate"
ng-attr-placeholder="{{ 'document.search_after_update_date' | translate }}"
current-text="{{ 'directive.datepicker.current' | translate }}"
clear-text="{{ 'directive.datepicker.clear' | translate }}"
close-text="{{ 'directive.datepicker.close' | translate }}"
datepicker-append-to-body="true"
ng-readonly="true" uib-datepicker-popup="{{ dateFormat }}" class="form-control"
ng-model="advsearch.after_update_date" datepicker-options="{ startingDay:1, showWeeks: false }"
ng-click="datepickerAfterUpdateOpened = !datepickerAfterUpdateOpened" is-open="datepickerAfterUpdateOpened" />
</div>
</div>
<div class="form-group">
<label class="control-label sr-only" for="inputSearchBeforeUpdateDate">{{ 'document.search_before_update_date' | translate }}</label>
<div class="col-sm-12">
<input type="text" id="inputSearchBeforeUpdateDate"
ng-attr-placeholder="{{ 'document.search_before_update_date' | translate }}"
current-text="{{ 'directive.datepicker.current' | translate }}"
clear-text="{{ 'directive.datepicker.clear' | translate }}"
close-text="{{ 'directive.datepicker.close' | translate }}"
datepicker-append-to-body="true"
ng-readonly="true" uib-datepicker-popup="{{ dateFormat }}" class="form-control"
ng-model="advsearch.before_update_date" datepicker-options="{ startingDay:1, showWeeks: false }"
ng-click="datepickerBeforeUpdateOpened = !datepickerBeforeUpdateOpened" is-open="datepickerBeforeUpdateOpened" />
</div>
</div>
<div class="form-group">
<label class="control-label sr-only" for="inputSearchTags">{{ 'document.search_tags' | translate }}</label>
<div class="col-sm-12">
@ -194,7 +224,9 @@
<span class="fas fa-random" ng-if="document.active_route" uib-tooltip="{{ document.current_step_name }}"></span>
<a href="#/document/view/{{ document.id }}" target="_blank" ng-click="$event.stopPropagation()"><span class="fas fa-link"></span></a>
<div class="pull-right text-muted small">{{ document.create_date | timeAgo: dateFormat }}</div>
<div class="pull-right text-muted small" uib-tooltip="{{ 'document.last_updated' | translate: { date: document.update_date } }}">
{{ document.create_date | timeAgo: dateFormat }}
</div>
<div class="tags small">
<span class="label label-info" ng-repeat="tag in document.tags" ng-style="{ 'background': tag.color }">

View File

@ -45,7 +45,7 @@
<div class="page-header">
<h1>
{{ document.title }}
<small>{{ document.create_date | date: dateFormat }}
<small uib-tooltip="{{ 'document.last_updated' | translate: { date: document.update_date } }}">{{ document.create_date | date: dateFormat }}
{{ 'document.view.by_creator' | translate }} <a href="#/user/{{ document.creator }}">{{ document.creator }}</a></small>
</h1>

View File

@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=17
db.version=18

View File

@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=17
db.version=18

View File

@ -1,16 +1,14 @@
package com.sismics.docs.rest;
import java.util.Date;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import org.junit.Assert;
import org.junit.Test;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import org.junit.Assert;
import org.junit.Test;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import java.util.Date;
/**
* Test the audit log resource.
@ -20,9 +18,11 @@ import com.sismics.util.filter.TokenBasedSecurityFilter;
public class TestAuditLogResource extends BaseJerseyTest {
/**
* Test the audit log resource.
*
* @throws Exception e
*/
@Test
public void testAuditLogResource() {
public void testAuditLogResource() throws Exception {
// Login auditlog1
clientUtil.createUser("auditlog1");
String auditlog1Token = clientUtil.login("auditlog1");
@ -91,6 +91,33 @@ public class TestAuditLogResource extends BaseJerseyTest {
Assert.assertTrue(logs.size() == 3);
Assert.assertEquals(countByClass(logs, "Document"), 1);
Assert.assertEquals(countByClass(logs, "Tag"), 2);
// Get document 1
json = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, auditlog1Token)
.get(JsonObject.class);
long update1Date = json.getJsonNumber("update_date").longValue();
// Add a file to the document
clientUtil.addFileToDocument("file/wikipedia.pdf", "wikipedia.pdf", auditlog1Token, document1Id);
// Get document 1
json = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, auditlog1Token)
.get(JsonObject.class);
Assert.assertEquals(update1Date, json.getJsonNumber("update_date").longValue());
// Get all logs for the document
json = target().path("/auditlog")
.queryParam("document", document1Id)
.request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, auditlog1Token)
.get(JsonObject.class);
logs = json.getJsonArray("logs");
Assert.assertEquals(4, logs.size());
Assert.assertEquals(countByClass(logs, "Document"), 1);
Assert.assertEquals(countByClass(logs, "Acl"), 2);
Assert.assertEquals(countByClass(logs, "File"), 1);
}
/**

View File

@ -103,6 +103,7 @@ public class TestDocumentResource extends BaseJerseyTest {
JsonArray documents = json.getJsonArray("documents");
JsonArray tags = documents.getJsonObject(0).getJsonArray("tags");
Assert.assertTrue(documents.size() == 2);
Assert.assertNotNull(documents.getJsonObject(0).get("update_date"));
Assert.assertEquals(document1Id, documents.getJsonObject(0).getString("id"));
Assert.assertEquals("eng", documents.getJsonObject(0).getString("language"));
Assert.assertEquals(1, documents.getJsonObject(0).getInt("file_count"));
@ -168,6 +169,10 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertEquals(2, searchDocuments("at:" + DateTimeFormat.forPattern("yyyy-MM").print(new Date().getTime()), document1Token));
Assert.assertEquals(2, searchDocuments("at:" + DateTimeFormat.forPattern("yyyy-MM-dd").print(new Date().getTime()), document1Token));
Assert.assertEquals(2, searchDocuments("after:2010 before:2040-08", document1Token));
Assert.assertEquals(2, searchDocuments("uat:" + DateTimeFormat.forPattern("yyyy").print(new Date().getTime()), document1Token));
Assert.assertEquals(2, searchDocuments("uat:" + DateTimeFormat.forPattern("yyyy-MM").print(new Date().getTime()), document1Token));
Assert.assertEquals(2, searchDocuments("uat:" + DateTimeFormat.forPattern("yyyy-MM-dd").print(new Date().getTime()), document1Token));
Assert.assertEquals(2, searchDocuments("uafter:2010 ubefore:2040-08", document1Token));
Assert.assertEquals(1, searchDocuments("tag:super", document1Token));
Assert.assertEquals(1, searchDocuments("shared:yes", document1Token));
Assert.assertEquals(2, searchDocuments("lang:eng", document1Token));
@ -204,6 +209,7 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertEquals("Public Domain", json.getString("rights"));
Assert.assertEquals("eng", json.getString("language"));
Assert.assertEquals(create1Date, json.getJsonNumber("create_date").longValue());
Assert.assertNotNull(json.get("update_date"));
tags = json.getJsonArray("tags");
Assert.assertEquals(1, tags.size());
Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id"));