#254: display documents in grid + concept of main file

This commit is contained in:
Benjamin Gamard 2018-11-01 16:27:35 +01:00
parent eb9e0e0543
commit cee82f39c2
18 changed files with 159 additions and 16 deletions

View File

@ -189,13 +189,28 @@ public class DocumentDao {
} }
/** /**
* Update a document. * Update a document and log the action.
* *
* @param document Document to update * @param document Document to update
* @param userId User ID * @param userId User ID
* @return Updated document * @return Updated document
*/ */
public Document update(Document document, String userId) { public Document update(Document document, String userId) {
Document documentDb = updateSilently(document);
// Create audit log
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);
return documentDb;
}
/**
* Update a document without audit log.
*
* @param document Document to update
* @return Updated document
*/
public Document updateSilently(Document document) {
EntityManager em = ThreadLocalContext.get().getEntityManager(); EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document // Get the document
@ -216,11 +231,9 @@ public class DocumentDao {
documentDb.setRights(document.getRights()); documentDb.setRights(document.getRights());
documentDb.setCreateDate(document.getCreateDate()); documentDb.setCreateDate(document.getCreateDate());
documentDb.setLanguage(document.getLanguage()); documentDb.setLanguage(document.getLanguage());
documentDb.setFileId(document.getFileId());
documentDb.setUpdateDate(new Date()); documentDb.setUpdateDate(new Date());
// Create audit log
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);
return documentDb; return documentDb;
} }

View File

@ -11,6 +11,11 @@ public class DocumentDto {
*/ */
private String id; private String id;
/**
* Main file ID.
*/
private String fileId;
/** /**
* Title. * Title.
*/ */
@ -114,6 +119,15 @@ public class DocumentDto {
this.id = id; this.id = id;
} }
public String getFileId() {
return fileId;
}
public DocumentDto setFileId(String fileId) {
this.fileId = fileId;
return this;
}
public String getTitle() { public String getTitle() {
return title; return title;
} }

View File

@ -4,10 +4,12 @@ import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.ContributorDao; import com.sismics.docs.core.dao.ContributorDao;
import com.sismics.docs.core.dao.DocumentDao; import com.sismics.docs.core.dao.DocumentDao;
import com.sismics.docs.core.dao.FileDao;
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent; import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext; import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.Contributor; import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.docs.core.model.jpa.Document; import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.TransactionUtil; import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -38,13 +40,25 @@ public class DocumentUpdatedAsyncListener {
} }
TransactionUtil.handle(() -> { TransactionUtil.handle(() -> {
// Update index // Get the document
DocumentDao documentDao = new DocumentDao(); DocumentDao documentDao = new DocumentDao();
Document document = documentDao.getById(event.getDocumentId()); Document document = documentDao.getById(event.getDocumentId());
if (document == null) { if (document == null) {
// Document deleted since event fired // Document deleted since event fired
return; return;
} }
// Set the main file
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.getByDocumentId(null, event.getDocumentId());
if (fileList.isEmpty()) {
document.setFileId(null);
} else {
document.setFileId(fileList.get(0).getId());
}
// Update database and index
documentDao.updateSilently(document);
AppContext.getInstance().getIndexingHandler().updateDocument(document); AppContext.getInstance().getIndexingHandler().updateDocument(document);
// Update contributors list // Update contributors list

View File

@ -29,6 +29,12 @@ public class Document implements Loggable {
@Column(name = "DOC_IDUSER_C", nullable = false, length = 36) @Column(name = "DOC_IDUSER_C", nullable = false, length = 36)
private String userId; private String userId;
/**
* Main file ID.
*/
@Column(name = "DOC_IDFILE_C", length = 36)
private String fileId;
/** /**
* Language (ISO 639-9). * Language (ISO 639-9).
*/ */
@ -137,6 +143,15 @@ public class Document implements Loggable {
this.userId = userId; this.userId = userId;
} }
public String getFileId() {
return fileId;
}
public Document setFileId(String fileId) {
this.fileId = fileId;
return this;
}
public String getTitle() { public String getTitle() {
return title; return title;
} }

View File

@ -219,7 +219,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
List<String> criteriaList = new ArrayList<>(); List<String> criteriaList = new ArrayList<>();
Map<String, String> documentSearchMap = Maps.newHashMap(); Map<String, String> documentSearchMap = Maps.newHashMap();
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, "); 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, d.DOC_IDFILE_C, ");
sb.append(" s.count c5, "); sb.append(" s.count c5, ");
sb.append(" f.count c6, "); sb.append(" f.count c6, ");
sb.append(" rs2.RTP_ID_C c7, rs2.RTP_NAME_C, d.DOC_UPDATEDATE_D c8 "); sb.append(" rs2.RTP_ID_C c7, rs2.RTP_NAME_C, d.DOC_UPDATEDATE_D c8 ");
@ -323,6 +323,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
documentDto.setDescription((String) o[i++]); documentDto.setDescription((String) o[i++]);
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime()); documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setLanguage((String) o[i++]); documentDto.setLanguage((String) o[i++]);
documentDto.setFileId((String) o[i++]);
Number shareCount = (Number) o[i++]; Number shareCount = (Number) o[i++];
documentDto.setShared(shareCount != null && shareCount.intValue() > 0); documentDto.setShared(shareCount != null && shareCount.intValue() > 0);
Number fileCount = (Number) o[i++]; Number fileCount = (Number) o[i++];

View File

@ -1 +1 @@
db.version=20 db.version=21

View File

@ -0,0 +1,4 @@
alter table T_DOCUMENT add column DOC_IDFILE_C varchar(36);
alter table T_DOCUMENT add constraint FK_DOC_IDFILE_C foreign key (DOC_IDFILE_C) references T_FILE (FIL_ID_C) on delete restrict on update restrict;
update T_CONFIG set CFG_VALUE_C = '21' where CFG_ID_C = 'DB_VERSION';

View File

@ -154,6 +154,16 @@ public class ClientUtil {
return authToken; return authToken;
} }
/**
* Add a file to a document.
*
* @param file File path
* @param filename Filename
* @param token Authentication token
* @param documentId Document ID
* @return File ID
* @throws IOException e
*/
public String addFileToDocument(String file, String filename, String token, String documentId) throws IOException { public String addFileToDocument(String file, String filename, String token, String documentId) throws IOException {
try (InputStream is = Resources.getResource(file).openStream()) { try (InputStream is = Resources.getResource(file).openStream()) {
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, filename); StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, filename);

View File

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

View File

@ -326,6 +326,7 @@ public class DocumentResource extends BaseResource {
* @apiSuccess {Object[]} documents List of documents * @apiSuccess {Object[]} documents List of documents
* @apiSuccess {String} documents.id ID * @apiSuccess {String} documents.id ID
* @apiSuccess {String} documents.highlight Search highlight (for fulltext search) * @apiSuccess {String} documents.highlight Search highlight (for fulltext search)
* @apiSuccess {String} documents.file_id Main file ID
* @apiSuccess {String} documents.title Title * @apiSuccess {String} documents.title Title
* @apiSuccess {String} documents.description Description * @apiSuccess {String} documents.description Description
* @apiSuccess {Number} documents.create_date Create date (timestamp) * @apiSuccess {Number} documents.create_date Create date (timestamp)
@ -395,6 +396,7 @@ public class DocumentResource extends BaseResource {
documents.add(Json.createObjectBuilder() documents.add(Json.createObjectBuilder()
.add("id", documentDto.getId()) .add("id", documentDto.getId())
.add("highlight", JsonUtil.nullable(documentDto.getHighlight())) .add("highlight", JsonUtil.nullable(documentDto.getHighlight()))
.add("file_id", JsonUtil.nullable(documentDto.getFileId()))
.add("title", documentDto.getTitle()) .add("title", documentDto.getTitle())
.add("description", JsonUtil.nullable(documentDto.getDescription())) .add("description", JsonUtil.nullable(documentDto.getDescription()))
.add("create_date", documentDto.getCreateTimestamp()) .add("create_date", documentDto.getCreateTimestamp())

View File

@ -368,6 +368,12 @@ public class FileResource extends BaseResource {
} }
} }
// Raise a document updated event
DocumentUpdatedAsyncEvent event = new DocumentUpdatedAsyncEvent();
event.setUserId(principal.getId());
event.setDocumentId(documentId);
ThreadLocalContext.get().addAsyncEvent(event);
// Always return OK // Always return OK
JsonObjectBuilder response = Json.createObjectBuilder() JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok"); .add("status", "ok");

View File

@ -12,6 +12,7 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
$scope.offset = 0; $scope.offset = 0;
$scope.currentPage = 1; $scope.currentPage = 1;
$scope.limit = _.isUndefined(localStorage.documentsPageSize) ? '10' : localStorage.documentsPageSize; $scope.limit = _.isUndefined(localStorage.documentsPageSize) ? '10' : localStorage.documentsPageSize;
$scope.displayMode = _.isUndefined(localStorage.displayMode) ? 'list' : localStorage.displayMode;
$scope.search = $state.params.search ? $state.params.search : ''; $scope.search = $state.params.search ? $state.params.search : '';
$scope.setSearch = function (search) { $scope.search = search }; $scope.setSearch = function (search) { $scope.search = search };
$scope.searchOpened = false; $scope.searchOpened = false;
@ -114,6 +115,13 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
$scope.loadDocuments(); $scope.loadDocuments();
}); });
/**
* Watch for display mode change.
*/
$scope.$watch('displayMode', function (next) {
localStorage.displayMode = next;
});
/** /**
* Display a document. * Display a document.
*/ */

View File

@ -41,6 +41,8 @@
"document": { "document": {
"navigation_up": "Go up one level", "navigation_up": "Go up one level",
"toggle_navigation": "Toggle folder navigation", "toggle_navigation": "Toggle folder navigation",
"display_mode_list": "Display documents in list",
"display_mode_grid": "Display documents in grid",
"search_simple": "Simple search", "search_simple": "Simple search",
"search_fulltext": "Fulltext search", "search_fulltext": "Fulltext search",
"search_creator": "Creator", "search_creator": "Creator",

View File

@ -169,6 +169,22 @@
</li> </li>
</ol> </ol>
<!-- Display mode (list or grid) -->
<div class="btn-group mt-10 ml-10 pull-right">
<span class="btn btn-default" ng-class="{ active: displayMode == 'list' }"
uib-tooltip="{{ 'document.display_mode_list' | translate }}"
tooltip-append-to-body="true"
ng-click="displayMode = 'list'">
<span class="fas fa-list"></span>
</span>
<span class="btn btn-default" ng-class="{ active: displayMode == 'grid' }"
uib-tooltip="{{ 'document.display_mode_grid' | translate }}"
tooltip-append-to-body="true"
ng-click="displayMode = 'grid'">
<span class="fas fa-th"></span>
</span>
</div>
<div class="btn-group mt-10 pull-right"> <div class="btn-group mt-10 pull-right">
<!-- Go up in the navigation --> <!-- Go up in the navigation -->
<button class="btn btn-default" ng-click="navigateUp()" <button class="btn btn-default" ng-click="navigateUp()"
@ -209,7 +225,7 @@
</table> </table>
<!-- Document list --> <!-- Document list -->
<table class="row table table-hover table-documents"> <table class="row table table-hover table-documents" ng-show="displayMode == 'list'">
<thead> <thead>
<tr> <tr>
<th class="col-xs-6" ng-click="sortDocuments(1)">{{ 'document.title' | translate }} <span class="fas fa-chevron-{{ sortColumn == 1 ? (asc ? 'down' : 'up') : '' }}"></span></th> <th class="col-xs-6" ng-click="sortDocuments(1)">{{ 'document.title' | translate }} <span class="fas fa-chevron-{{ sortColumn == 1 ? (asc ? 'down' : 'up') : '' }}"></span></th>
@ -253,6 +269,39 @@
</tbody> </tbody>
</table> </table>
<!-- Document grid -->
<div class="row table-documents" ng-show="displayMode == 'grid'">
<div ng-if="!documents" class="col-md-12 text-center">
<span class="fas fa-circle-notch fa-spin"></span>
</div>
<div ng-if="totalDocuments == 0" class=" col-md-12 text-center">
<span ng-if="search.length == 0">{{ 'document.no_documents' | translate }}</span>
<span ng-if="search.length > 0" translate="document.search_empty" translate-values="{ search: search }"></span>
</div>
<div class="col-md-12 row">
<div ng-repeat-start="document in documents" class="col-md-4">
<div class="thumbnail" ng-class="{ active: $stateParams.id == document.id }">
<a href="#/document/view/{{ document.id }}" class="file-thumbnail">
<img class="img-responsive" ng-src="../api/file/{{ document.file_id }}/data?size=thumb" />
</a>
<div class="file-info">
<div></div>
<div class="v-align file-name text-center">
{{ document.title }} ({{ document.file_count }})
<span class="fas fa-share" ng-if="document.shared" uib-tooltip="{{ 'document.shared' | translate }}"></span>
<span class="fas fa-random" ng-if="document.active_route" uib-tooltip="{{ document.current_step_name }}"></span>
</div>
<div></div>
</div>
</div>
</div>
<div class="clearfix" ng-repeat-end ng-if="($index + 1) % 3 == 0"></div>
</div>
</div>
<div class="text-center pagination-box"> <div class="text-center pagination-box">
<ul uib-pagination <ul uib-pagination
ng-if="paginationShown" ng-if="paginationShown"

View File

@ -187,6 +187,10 @@ ul.tag-tree {
// File thumbnails // File thumbnails
.thumbnail { .thumbnail {
&.active {
border: 1px solid #2ab2dc;
}
.file-processing-indicator { .file-processing-indicator {
position: absolute; position: absolute;
z-index: 2; z-index: 2;

View File

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

View File

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

View File

@ -117,6 +117,7 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertNotNull(documents.getJsonObject(0).get("update_date")); Assert.assertNotNull(documents.getJsonObject(0).get("update_date"));
Assert.assertEquals(document1Id, documents.getJsonObject(0).getString("id")); Assert.assertEquals(document1Id, documents.getJsonObject(0).getString("id"));
Assert.assertEquals("eng", documents.getJsonObject(0).getString("language")); Assert.assertEquals("eng", documents.getJsonObject(0).getString("language"));
Assert.assertEquals(file1Id, documents.getJsonObject(0).getString("file_id"));
Assert.assertEquals(1, documents.getJsonObject(0).getInt("file_count")); Assert.assertEquals(1, documents.getJsonObject(0).getInt("file_count"));
Assert.assertEquals(2, tags.size()); Assert.assertEquals(2, tags.size());
Assert.assertEquals(tag2Id, tags.getJsonObject(0).getString("id")); Assert.assertEquals(tag2Id, tags.getJsonObject(0).getString("id"));