From 12fd807493072c54948da1e14939626202dd27d1 Mon Sep 17 00:00:00 2001 From: Eike Cochu <eike@cochu.com> Date: Thu, 7 Apr 2016 03:12:25 +0200 Subject: [PATCH] added textentities as entities, added ui view --- .../vipra/rest/resource/ArticleResource.java | 2 +- .../rest/resource/TextEntityResource.java | 104 ++++++++++++++++++ .../vipra/cmd/option/DeleteModelCommand.java | 3 + .../de/vipra/cmd/option/ImportCommand.java | 33 ++++-- .../de/vipra/cmd/text/SpotlightResponse.java | 18 +-- vipra-ui/app/html/articles/index.html | 3 +- vipra-ui/app/html/articles/show.html | 8 +- .../app/html/directives/article-link.html | 1 + vipra-ui/app/html/directives/entity-link.html | 6 +- vipra-ui/app/html/directives/entity-menu.html | 2 +- vipra-ui/app/html/directives/topic-link.html | 4 +- vipra-ui/app/html/directives/topic-menu.html | 1 - vipra-ui/app/html/directives/word-link.html | 2 +- vipra-ui/app/html/entities/articles.html | 2 +- vipra-ui/app/html/entities/index.html | 43 +++++++- vipra-ui/app/html/index.html | 4 +- vipra-ui/app/html/topics/index.html | 2 +- vipra-ui/app/html/topics/show.html | 4 +- vipra-ui/app/html/words/index.html | 14 +-- vipra-ui/app/html/words/topics.html | 2 +- vipra-ui/app/index.html | 3 + vipra-ui/app/js/controllers.js | 41 ++++++- vipra-ui/app/js/directives.js | 35 ++++-- vipra-ui/app/js/factories.js | 4 + .../java/de/vipra/util/model/ArticleFull.java | 16 +-- .../java/de/vipra/util/model/TextEntity.java | 51 ++++----- .../de/vipra/util/model/TextEntityCount.java | 37 +++++++ .../de/vipra/util/model/TextEntityFull.java | 98 +++++++++++++++++ 28 files changed, 448 insertions(+), 95 deletions(-) create mode 100644 vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java create mode 100644 vipra-util/src/main/java/de/vipra/util/model/TextEntityCount.java create mode 100644 vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java index 29cc5aac..2e647eb4 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java @@ -74,7 +74,7 @@ public class ArticleResource { query.criteria("words.id", word); if (entity != null && !entity.isEmpty()) - query.criteria("entities.entity", entity); + query.criteria("entities.entity.id", entity); final List<ArticleFull> articles = dbArticles.getMultiple(query); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java new file mode 100644 index 00000000..25c59ed4 --- /dev/null +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java @@ -0,0 +1,104 @@ +package de.vipra.rest.resource; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import de.vipra.rest.Messages; +import de.vipra.rest.model.APIError; +import de.vipra.rest.model.ResponseWrapper; +import de.vipra.util.Config; +import de.vipra.util.StringUtils; +import de.vipra.util.ex.ConfigException; +import de.vipra.util.model.TextEntityFull; +import de.vipra.util.model.TopicModel; +import de.vipra.util.service.MongoService; +import de.vipra.util.service.Service.QueryBuilder; + +@Path("entities") +public class TextEntityResource { + + @Context + UriInfo uri; + + final MongoService<TextEntityFull, String> dbEntities; + + public TextEntityResource(@Context final ServletContext servletContext) throws ConfigException, IOException { + final Config config = Config.getConfig(); + dbEntities = MongoService.getDatabaseService(config, TextEntityFull.class); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getEntities(@QueryParam("topicModel") final String topicModel, @QueryParam("skip") final Integer skip, + @QueryParam("limit") final Integer limit, @QueryParam("sort") @DefaultValue("id") final String sortBy, + @QueryParam("fields") final String fields) { + final ResponseWrapper<List<TextEntityFull>> res = new ResponseWrapper<>(); + + if (res.hasErrors()) + return res.badRequest(); + + try { + final QueryBuilder query = QueryBuilder.builder().skip(skip).limit(limit).sortBy(sortBy); + if (fields != null && !fields.isEmpty()) + query.fields(true, StringUtils.getFields(fields)); + + if (topicModel != null && !topicModel.isEmpty()) + query.criteria("topicModel", new TopicModel(topicModel)); + + final List<TextEntityFull> entities = dbEntities.getMultiple(query); + + if ((skip != null && skip > 0) || (limit != null && limit > 0)) + res.addHeader("total", dbEntities.count(null)); + else + res.addHeader("total", entities.size()); + + return res.ok(entities); + } catch (final Exception e) { + e.printStackTrace(); + res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); + return res.badRequest(); + } + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{id}") + public Response getEntity(@PathParam("id") final String id, @QueryParam("fields") final String fields) { + final ResponseWrapper<TextEntityFull> res = new ResponseWrapper<>(); + if (id == null || id.trim().length() == 0) { + res.addError(new APIError(Response.Status.BAD_REQUEST, "ID is empty", String.format(Messages.BAD_REQUEST, "id cannot be empty"))); + return res.badRequest(); + } + + TextEntityFull entity; + try { + entity = dbEntities.getSingle(id, StringUtils.getFields(fields)); + } catch (final Exception e) { + e.printStackTrace(); + res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); + return res.badRequest(); + } + + if (entity != null) { + return res.ok(entity); + } else { + res.addError(new APIError(Response.Status.NOT_FOUND, "Resource not found", String.format(Messages.NOT_FOUND, "entity", id))); + return res.notFound(); + } + } + +} diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java index af9f91b3..00114a42 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java @@ -9,6 +9,7 @@ import de.vipra.util.Config; import de.vipra.util.ConsoleUtils; import de.vipra.util.model.ArticleFull; import de.vipra.util.model.SequenceFull; +import de.vipra.util.model.TextEntityFull; import de.vipra.util.model.TopicFull; import de.vipra.util.model.TopicModel; import de.vipra.util.model.WindowFull; @@ -33,6 +34,7 @@ public class DeleteModelCommand implements Command { final MongoService<WindowFull, Integer> dbWindows = MongoService.getDatabaseService(config, WindowFull.class); final MongoService<SequenceFull, ObjectId> dbSequences = MongoService.getDatabaseService(config, SequenceFull.class); final MongoService<WordFull, String> dbWords = MongoService.getDatabaseService(config, WordFull.class); + final MongoService<TextEntityFull, String> dbEntities = MongoService.getDatabaseService(config, TextEntityFull.class); for (final String name : names) { final File modelDir = new File(config.getDataDirectory(), name); @@ -51,6 +53,7 @@ public class DeleteModelCommand implements Command { dbWindows.deleteMultiple(builder); dbSequences.deleteMultiple(builder); dbWords.deleteMultiple(builder); + dbEntities.deleteMultiple(builder); } } diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java index fa09572a..00191346 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java @@ -6,7 +6,9 @@ import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,7 +36,8 @@ import de.vipra.util.ex.ConfigException; import de.vipra.util.ex.DatabaseException; import de.vipra.util.model.ArticleFull; import de.vipra.util.model.ArticleStats; -import de.vipra.util.model.TextEntity; +import de.vipra.util.model.TextEntityCount; +import de.vipra.util.model.TextEntityFull; import de.vipra.util.model.TopicModel; import de.vipra.util.model.TopicModelConfig; import de.vipra.util.model.TopicModelFull; @@ -74,12 +77,14 @@ public class ImportCommand implements Command { private MongoService<ArticleFull, ObjectId> dbArticles; private MongoService<TopicModelFull, String> dbTopicModels; private MongoService<WordFull, String> dbWords; + private MongoService<TextEntityFull, String> dbEntities; private TopicModelConfig modelConfig; private SpotlightAnalyzer spotlightAnalyzer; private Filebase filebase; private Processor processor; private ArticleBuffer buffer; private TopicModelFull topicModel; + private Set<TextEntityFull> newTextEntities; /** * Import command to import articles into the database, do topic modeling @@ -153,14 +158,21 @@ public class ImportCommand implements Command { // spotlight analysis if (spotlightAnalyzer != null) { final SpotlightResponse spotlightResponse = spotlightAnalyzer.analyze(article.getText()); - final List<TextEntity> textEntities = spotlightResponse.getEntities(); - article.setEntities(textEntities); - // insert entities into text String articleText = article.getText(); - for (final TextEntity textEntity : textEntities) - articleText = articleText.replaceAll("(?i)\\b" + Pattern.quote(textEntity.getEntity()) + "\\b(?![^<]*>|[^<>]*</)", - Matcher.quoteReplacement(textEntity.aTag())); + + for (final TextEntityCount textEntityCount : spotlightResponse.getEntities()) { + // get new text entity + final TextEntityFull newTextEntity = new TextEntityFull(textEntityCount.getEntity()); + newTextEntity.setTopicModel(new TopicModel(topicModel.getId())); + newTextEntities.add(newTextEntity); + + // insert entity into text + articleText = articleText.replaceAll("(?i)\\b" + Pattern.quote(textEntityCount.getEntity().getId()) + "\\b(?![^<]*>|[^<>]*</)", + Matcher.quoteReplacement(textEntityCount.getEntity().aTag())); + } + + article.setEntities(spotlightResponse.getEntities()); article.setText(articleText); } @@ -251,6 +263,7 @@ public class ImportCommand implements Command { buffer = new ArticleBuffer(dbArticles); filebase = new Filebase(modelConfig, config.getDataDirectory()); topicModel = new TopicModelFull(modelConfig.getName(), modelConfig); + newTextEntities = new HashSet<>(); dbTopicModels.replaceSingle(topicModel); @@ -281,6 +294,11 @@ public class ImportCommand implements Command { } dbWords.createMultiple(newWords); + /* + * add new entities + */ + dbEntities.createMultiple(newTextEntities); + /* * run information */ @@ -292,6 +310,7 @@ public class ImportCommand implements Command { config = Config.getConfig(); dbArticles = MongoService.getDatabaseService(config, ArticleFull.class); dbTopicModels = MongoService.getDatabaseService(config, TopicModelFull.class); + dbEntities = MongoService.getDatabaseService(config, TextEntityFull.class); dbWords = MongoService.getDatabaseService(config, WordFull.class); processor = new Processor(); for (final TopicModelConfig modelConfig : config.getTopicModelConfigs(models)) { diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java b/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java index e1f904cf..19f2cc40 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import de.vipra.util.CountMap; import de.vipra.util.model.TextEntity; +import de.vipra.util.model.TextEntityCount; @JsonIgnoreProperties(ignoreUnknown = true) public class SpotlightResponse { @@ -27,7 +28,7 @@ public class SpotlightResponse { this.resources = resources; } - public List<TextEntity> getEntities() { + public List<TextEntityCount> getEntities() { final CountMap<String> textEntitiesCount = new CountMap<>(resources.size()); final Set<TextEntity> textEntities = new HashSet<>(resources.size()); @@ -54,14 +55,17 @@ public class SpotlightResponse { } // insert count - for (final TextEntity textEntity : textEntities) - textEntity.setCount(textEntitiesCount.get(textEntity.getEntity())); + final List<TextEntityCount> textEntitiesCountList = new ArrayList<>(textEntities.size()); + for (final TextEntity textEntity : textEntities) { + final TextEntityCount textEntityCount = new TextEntityCount(); + textEntityCount.setEntity(textEntity); + textEntityCount.setCount(textEntitiesCount.get(textEntity.getId())); + textEntitiesCountList.add(textEntityCount); + } - // to list and sort - final List<TextEntity> textEntitiesList = new ArrayList<>(textEntities); - Collections.sort(textEntitiesList, Comparator.reverseOrder()); + Collections.sort(textEntitiesCountList, Comparator.reverseOrder()); - return textEntitiesList; + return textEntitiesCountList; } } diff --git a/vipra-ui/app/html/articles/index.html b/vipra-ui/app/html/articles/index.html index 048e680a..413ca3c4 100644 --- a/vipra-ui/app/html/articles/index.html +++ b/vipra-ui/app/html/articles/index.html @@ -25,8 +25,7 @@ <tbody> <tr ng-repeat="article in articles"> <td> - <a ui-sref="articles.show({id: article.id})" ng-bind="::article.title"></a> - <span class="badge pull-right" ng-bind="::article.topicsCount" ng-attr-title="{{::article.topicsCount}} topic(s)"></span> + <article-link article="::article"/> </td> </tr> </tbody> diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index 545e7f54..709ce323 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -62,7 +62,7 @@ <tr ng-repeat="topic in article.topics | orderBy:articlesShowModels.topicsSort" ng-mouseenter="highlightSlice(topic.topic.id, true)" ng-mouseleave="highlightSlice(topic.topic.id, false)"> <td class="text-right" ng-bind-template="{{(topic.share*100).toFixed(0)}}%"></td> <td> - <topic-link topic="topic.topic" /> + <topic-link topic="::topic.topic" /> </td> <td> <span class="colorbox" style="background:{{::topic.color}}"></span> @@ -89,7 +89,7 @@ <tr ng-repeat="simArticle in article.similarArticles | orderBy:articlesShowModels.similarSort"> <td class="text-right" ng-bind-template="{{::simArticle.share}}%"></td> <td> - <a ui-sref="articles.show({id: simArticle.article.id})" ng-attr-title="{{::simArticle.article.title}}" ng-bind="::simArticle.article.title"></a> + <article-link article="::simArticle.article" /> </td> </tr> </tbody> @@ -109,14 +109,14 @@ <table class="table table-bordered table-condensed table-fixed"> <thead> <tr> - <th ng-model="articlesShowModels.entitiesSort" sort-by="entity">Entity</th> + <th ng-model="articlesShowModels.entitiesSort" sort-by="entity.id">Entity</th> <th ng-model="articlesShowModels.entitiesSort" sort-by="count">Count</th> </tr> </thead> <tbody> <tr ng-repeat="entity in entities | orderBy:articlesShowModels.entitiesSort"> <td> - <entity-link entity="::entity" /> + <entity-link entity="::entity.entity" /> </td> <td ng-bind="::entity.count"></td> </tr> diff --git a/vipra-ui/app/html/directives/article-link.html b/vipra-ui/app/html/directives/article-link.html index 9a087e50..7a64f2c7 100644 --- a/vipra-ui/app/html/directives/article-link.html +++ b/vipra-ui/app/html/directives/article-link.html @@ -3,4 +3,5 @@ <span ng-bind="article.title"></span> <ng-transclude/> </a> + <span class="badge pull-right" ng-bind="::article.topicsCount" ng-attr-title="{{::article.topicsCount}} topic(s)" ng-if="::showBadge"></span> </span> diff --git a/vipra-ui/app/html/directives/entity-link.html b/vipra-ui/app/html/directives/entity-link.html index a54f5363..569cdb43 100644 --- a/vipra-ui/app/html/directives/entity-link.html +++ b/vipra-ui/app/html/directives/entity-link.html @@ -1,7 +1,7 @@ <span> - <entity-menu entity="entity" /> - <a class="entity-link" ui-sref="entities.show({id:entity.entity})"> - <span ng-bind="entity.entity"></span> + <entity-menu entity="entity" ng-if="::showMenu" /> + <a class="entity-link" ui-sref="entities.show({id:entity.id})"> + <span ng-bind="entity.id"></span> <ng-transclude/> </a> </span> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/entity-menu.html b/vipra-ui/app/html/directives/entity-menu.html index 08f1ca79..bd253dcd 100644 --- a/vipra-ui/app/html/directives/entity-menu.html +++ b/vipra-ui/app/html/directives/entity-menu.html @@ -3,7 +3,7 @@ <i class="fa fa-caret-down"></i> </a> <ul class="dropdown-menu" ng-class="{'dropdown-menu-right':dropdownRight}"> - <li><a ui-sref="entities.show.articles({id:entity.entity})">Articles</a></li> + <li><a ui-sref="entities.show.articles({id:entity.id})">Articles</a></li> <li role="separator" class="divider"></li> <li><a ng-href="{{entity.url}}" target="_blank"><span class="dbpedia-logo"></span> DBPedia</a></li> </ul> diff --git a/vipra-ui/app/html/directives/topic-link.html b/vipra-ui/app/html/directives/topic-link.html index 4cd594c7..e0821bd9 100644 --- a/vipra-ui/app/html/directives/topic-link.html +++ b/vipra-ui/app/html/directives/topic-link.html @@ -1,8 +1,8 @@ <span> - <topic-menu topic="topic" /> + <topic-menu topic="topic" ng-if="::showMenu" /> <a class="topic-link" ui-sref="topics.show({id:topic.id})"> <span ng-bind="topic.name"></span> <ng-transclude/> </a> - <span class="badge pull-right" ng-bind="::topic.articlesCount" ng-attr-title="{{::topic.articlesCount}} article(s)"></span> + <span class="badge pull-right" ng-bind="::topic.articlesCount" ng-attr-title="{{::topic.articlesCount}} article(s)" ng-if="::showBadge"></span> </span> diff --git a/vipra-ui/app/html/directives/topic-menu.html b/vipra-ui/app/html/directives/topic-menu.html index f08e2a20..02497524 100644 --- a/vipra-ui/app/html/directives/topic-menu.html +++ b/vipra-ui/app/html/directives/topic-menu.html @@ -3,7 +3,6 @@ <i class="fa fa-caret-down"></i> </a> <ul class="dropdown-menu" ng-class="{'dropdown-menu-right':dropdownRight}"> - <li><a ui-sref="topics.show({id:topic.id})">Show</a></li> <li><a ui-sref="network({type:'topics',id:topic.id})">Network</a></li> <li><a ui-sref="topics.show.articles({id:topic.id})">Articles</a></li> <li role="separator" class="divider"></li> diff --git a/vipra-ui/app/html/directives/word-link.html b/vipra-ui/app/html/directives/word-link.html index 73cc32b8..06ae886f 100644 --- a/vipra-ui/app/html/directives/word-link.html +++ b/vipra-ui/app/html/directives/word-link.html @@ -1,4 +1,4 @@ <span> - <word-menu word="word" /> + <word-menu word="word" ng-if="::showMenu" /> <a ui-sref="words.show({id: word.id})" ng-bind="word.id"></a> </span> \ No newline at end of file diff --git a/vipra-ui/app/html/entities/articles.html b/vipra-ui/app/html/entities/articles.html index be1fda44..ba2d7bc1 100644 --- a/vipra-ui/app/html/entities/articles.html +++ b/vipra-ui/app/html/entities/articles.html @@ -38,7 +38,7 @@ <tbody> <tr ng-repeat="article in articles"> <td> - <a ui-sref="articles.show({id: article.id})" ng-bind="::article.title"></a> + <article-link article="::article"/> </td> </tr> </tbody> diff --git a/vipra-ui/app/html/entities/index.html b/vipra-ui/app/html/entities/index.html index 0b019785..63d53c59 100644 --- a/vipra-ui/app/html/entities/index.html +++ b/vipra-ui/app/html/entities/index.html @@ -1 +1,42 @@ -<div ng-cloak ui-view></div> \ No newline at end of file +<div class="container" ng-cloak ng-hide="!rootModels.topicModel || state.name !== 'entities'"> + <div class="row"> + <div class="col-md-12 text-center"> + <pagination total="entitiesTotal" page="entitiesIndexModels.page" limit="entitiesIndexModels.limit" /> + </div> + </div> + <div class="row"> + <div class="col-md-12"> + <div class="panel panel-default"> + <div class="panel-heading"> + Found + <ng-pluralize count="entitiesTotal||0" when="{0:'no entities',1:'1 entity',other:'{} entities'}"></ng-pluralize> in the database. + <span ng-show="entitiesTotal"> + Sort by + <ol class="nya-bs-select nya-bs-condensed" ng-model="entitiesIndexModels.sortkey"> + <li value="id" class="nya-bs-option"><a>Entity</a></li> + </ol> + <sort-dir ng-model="entitiesIndexModels.sortdir" /> + </span> + </div> + <table class="table table-hover table-condensed"> + <tbody> + <tr ng-repeat="entity in entities"> + <td> + <entity-link entity="::entity" /> + </td> + </tr> + </tbody> + </table> + <div class="panel-footer"> + Page <span ng-bind="entitiesIndexModels.page||1"></span> of <span ng-bind="maxPage||1"></span> + </div> + </div> + </div> + </div> + <div class="row"> + <div class="col-md-12 text-center"> + <pagination total="entitiesTotal" page="entitiesIndexModels.page" limit="entitiesIndexModels.limit" /> + </div> + </div> +</div> +<div ng-cloak ui-view></div> diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html index e76878b5..cf456ef8 100644 --- a/vipra-ui/app/html/index.html +++ b/vipra-ui/app/html/index.html @@ -14,7 +14,7 @@ <h4>Latest articles</h4> <ul class="list-unstyled"> <li class="ellipsis" ng-repeat="article in latestArticles"> - <a ui-sref="articles.show({id:article.id})" ng-bind="article.title"></a> + <article-link article="::article" badge="false" menu="false"/> </li> </ul> </div> @@ -22,7 +22,7 @@ <h4>Latest topics</h4> <ul class="list-unstyled"> <li class="ellipsis" ng-repeat="topic in latestTopics"> - <a ui-sref="topics.show({id:topic.id})" ng-bind="topic.name"></a> + <topic-link topic="::topic" badge="false" menu="false"/> </li> </ul> </div> diff --git a/vipra-ui/app/html/topics/index.html b/vipra-ui/app/html/topics/index.html index a39d5eb0..3754a27d 100644 --- a/vipra-ui/app/html/topics/index.html +++ b/vipra-ui/app/html/topics/index.html @@ -24,7 +24,7 @@ <tbody> <tr ng-repeat="topic in topics"> <td> - <topic-link topic="topic" /> + <topic-link topic="::topic" /> </td> </tr> </tbody> diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index 5c745179..ceb2a222 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -92,7 +92,7 @@ <div class="checkbox checkbox-condensed" ng-class="{selected:word.selected}"> <input tabindex="0" type="checkbox" ng-model="word.selected" ng-attr-id="{{::word.id}}" ng-change="redrawWordEvolutionChart()"> <label class="check" ng-attr-for="{{::word.id}}"> - <word-link word="word" /> + <word-link word="::word" /> </label> </div> </li> @@ -123,7 +123,7 @@ <tbody> <tr ng-repeat="word in sequence.words | orderBy:topicsShowModels.seqSortWords"> <td> - <word-link word="word" /> + <word-link word="::word" /> </td> <td ng-bind="word.probability.toFixed(4)"></td> </tr> diff --git a/vipra-ui/app/html/words/index.html b/vipra-ui/app/html/words/index.html index 16a03a7f..eb1c10cd 100644 --- a/vipra-ui/app/html/words/index.html +++ b/vipra-ui/app/html/words/index.html @@ -11,18 +11,18 @@ Found <ng-pluralize count="wordsTotal||0" when="{0:'no words',1:'1 word',other:'{} words'}"></ng-pluralize> in the database. <span ng-show="wordsTotal"> - Sort by - <ol class="nya-bs-select nya-bs-condensed" ng-model="wordsIndexModels.sortkey"> - <li value="id" class="nya-bs-option"><a>Word</a></li> - </ol> - <sort-dir ng-model="wordsIndexModels.sortdir" /> - </span> + Sort by + <ol class="nya-bs-select nya-bs-condensed" ng-model="wordsIndexModels.sortkey"> + <li value="id" class="nya-bs-option"><a>Word</a></li> + </ol> + <sort-dir ng-model="wordsIndexModels.sortdir" /> + </span> </div> <table class="table table-hover table-condensed"> <tbody> <tr ng-repeat="word in words"> <td> - <word-link word="word" /> + <word-link word="::word" /> </td> </tr> </tbody> diff --git a/vipra-ui/app/html/words/topics.html b/vipra-ui/app/html/words/topics.html index f96c9696..162d93f3 100644 --- a/vipra-ui/app/html/words/topics.html +++ b/vipra-ui/app/html/words/topics.html @@ -37,7 +37,7 @@ <tbody> <tr ng-repeat="topic in topics"> <td> - <topic-link topic="topic" /> + <topic-link topic="::topic" /> </td> </tr> </tbody> diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html index ddb769d7..f65433a5 100644 --- a/vipra-ui/app/index.html +++ b/vipra-ui/app/index.html @@ -64,6 +64,9 @@ <li ui-sref-active="active"> <a tabindex="0" ui-sref="topics"><span class="mnemonic">T</span>opics</a> </li> + <li ui-sref-active="active"> + <a tabindex="0" ui-sref="entities">E<span class="mnemonic">n</span>tities</a> + </li> <li ui-sref-active="active"> <a tabindex="0" ui-sref="words"><span class="mnemonic">W</span>ords</a> </li> diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index 8f10c902..6510ac23 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -119,6 +119,15 @@ } }); + hotkeys.add({ + combo: 'n', + description: 'Go to entities', + callback: function() { + if ($scope.rootModels.topicModel && $state.current.name !== 'entities') + $state.transitionTo('entities'); + } + }); + hotkeys.add({ combo: 'w', description: 'Go to words', @@ -653,7 +662,7 @@ // calculate share from divergence if($scope.article.similarArticles) { for(var i = 0; i < $scope.article.similarArticles.length; i++) - $scope.article.similarArticles[i].share = ((1 - $scope.article.similarArticles[i].divergence) * 100).toFixed(0); + $scope.article.similarArticles[i].share = Math.round(((1 - $scope.article.similarArticles[i].divergence) * 100)); } // take topic model from article @@ -1003,8 +1012,34 @@ * Entity Controllers ****************************************************************************/ - app.controller('EntitiesIndexController', ['$scope', - function($scope) { + app.controller('EntitiesIndexController', ['$scope', '$state', 'EntityFactory', + function($scope, $state, EntityFactory) { + + // page was reloaded, choose topic model + if (!$scope.rootModels.topicModel && $state.current.name === 'entities') + $scope.chooseTopicModel(); + + $scope.entitiesIndexModels = { + sortkey: 'id', + sortdir: true, + page: 1, + limit: 100 + }; + + $scope.$watchGroup(['entitiesIndexModels.page', 'entitiesIndexModels.sortkey', 'entitiesIndexModels.sortdir', 'rootModels.topicModel'], function() { + if (!$scope.rootModels.topicModel) return; + + EntityFactory.query({ + topicModel: $scope.rootModels.topicModel.id, + skip: ($scope.entitiesIndexModels.page - 1) * $scope.entitiesIndexModels.limit, + limit: $scope.entitiesIndexModels.limit, + sort: ($scope.entitiesIndexModels.sortdir ? '' : '-') + $scope.entitiesIndexModels.sortkey + }, function(data, headers) { + $scope.entities = data; + $scope.entitiesTotal = headers("V-Total"); + $scope.maxPage = Math.ceil($scope.entitiesTotal / $scope.entitiesIndexModels.limit); + }); + }); } ]); diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index 78b9736d..41f83f96 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -14,48 +14,67 @@ app.directive('topicLink', [function() { return { scope: { - topic: '=' + topic: '=', + badge: '@', + menu: '@' }, restrict: 'E', replace: true, transclude: true, - templateUrl: 'html/directives/topic-link.html' + templateUrl: 'html/directives/topic-link.html', + link: function($scope) { + $scope.showBadge = $scope.badge !== 'false'; + $scope.showMenu = $scope.menu !== 'false'; + } }; }]); app.directive('wordLink', [function() { return { scope: { - word: '=' + word: '=', + menu: '@' }, restrict: 'E', replace: true, transclude: true, - templateUrl: 'html/directives/word-link.html' + templateUrl: 'html/directives/word-link.html', + link: function($scope) { + $scope.showBadge = $scope.badge !== 'false'; + $scope.showMenu = $scope.menu !== 'false'; + } }; }]); app.directive('articleLink', [function() { return { scope: { - article: '=' + article: '=', + badge: '@' }, restrict: 'E', replace: true, transclude: true, - templateUrl: 'html/directives/article-link.html' + templateUrl: 'html/directives/article-link.html', + link: function($scope) { + $scope.showBadge = $scope.badge !== 'false'; + } }; }]); app.directive('entityLink', [function() { return { scope: { - entity: '=' + entity: '=', + menu: '@' }, restrict: 'E', replace: true, transclude: true, - templateUrl: 'html/directives/entity-link.html' + templateUrl: 'html/directives/entity-link.html', + link: function($scope) { + $scope.showMenu = $scope.menu !== 'false'; + } }; }]); diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js index b331235d..4fe9d147 100644 --- a/vipra-ui/app/js/factories.js +++ b/vipra-ui/app/js/factories.js @@ -76,4 +76,8 @@ return $myResource(Vipra.config.restUrl + '/words/:id'); }]); + app.factory('EntityFactory', ['$myResource', function($myResource) { + return $myResource(Vipra.config.restUrl + '/entities/:id'); + }]); + })(); \ No newline at end of file diff --git a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java index 20b09a25..98a7453f 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java @@ -74,7 +74,7 @@ public class ArticleFull implements Model<ObjectId>, Serializable { @Embedded @QueryIgnore(all = true) - private List<TextEntity> entities; + private List<TextEntityCount> entities; @Embedded @QueryIgnore(multi = true) @@ -214,11 +214,11 @@ public class ArticleFull implements Model<ObjectId>, Serializable { this.words = words; } - public List<TextEntity> getEntities() { + public List<TextEntityCount> getEntities() { return entities; } - public void setEntities(final List<TextEntity> entities) { + public void setEntities(final List<TextEntityCount> entities) { this.entities = entities; } @@ -262,14 +262,14 @@ public class ArticleFull implements Model<ObjectId>, Serializable { public String[] entitiesWithTypes() { int size = 0; - for (final TextEntity textEntity : entities) { + for (final TextEntityCount textEntity : entities) { size++; - if (textEntity.getTypes() != null) - size += textEntity.getTypes().size(); + if (textEntity.getEntity().getTypes() != null) + size += textEntity.getEntity().getTypes().size(); } final List<String> entitiesWithTypes = new ArrayList<>(size); - for (final TextEntity textEntity : entities) - entitiesWithTypes.addAll(textEntity.entityWithTypes()); + for (final TextEntityCount textEntity : entities) + entitiesWithTypes.addAll(textEntity.getEntity().entityWithTypes()); return entitiesWithTypes.toArray(new String[size]); } diff --git a/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java b/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java index 275e349b..4c5b88b5 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java @@ -4,36 +4,36 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import org.mongodb.morphia.annotations.Embedded; +import org.mongodb.morphia.annotations.Entity; +import org.mongodb.morphia.annotations.Id; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") -@Embedded -public class TextEntity implements Comparable<TextEntity>, Serializable { +@Entity(value = "textentities", noClassnameStored = true) +public class TextEntity implements Model<String>, Serializable { - private String entity; + @Id + private String id; private String url; - private Integer count; - private List<String> types; public TextEntity() {} - public TextEntity(final String entity, final String url) { - this.entity = entity; + public TextEntity(final String id, final String url) { + this.id = id; this.url = url; } - public String getEntity() { - return entity; + public String getId() { + return id; } - public void setEntity(final String entity) { - this.entity = entity; + public void setId(final String id) { + this.id = id; } public String getUrl() { @@ -44,14 +44,6 @@ public class TextEntity implements Comparable<TextEntity>, Serializable { this.url = url; } - public Integer getCount() { - return count; - } - - public void setCount(final Integer count) { - this.count = count; - } - public List<String> getTypes() { return types; } @@ -61,12 +53,12 @@ public class TextEntity implements Comparable<TextEntity>, Serializable { } public String aTag() { - return "<a href=\"" + url + "\">" + entity + "</a>"; + return "<a href=\"" + url + "\">" + id + "</a>"; } public List<String> entityWithTypes() { final List<String> entityWithTypes = new ArrayList<>(types.size() + 1); - entityWithTypes.add(entity.toLowerCase()); + entityWithTypes.add(id.toLowerCase()); for (final String type : types) entityWithTypes.add(type.toLowerCase()); return entityWithTypes; @@ -76,7 +68,7 @@ public class TextEntity implements Comparable<TextEntity>, Serializable { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((entity == null) ? 0 : entity.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @@ -89,22 +81,17 @@ public class TextEntity implements Comparable<TextEntity>, Serializable { if (getClass() != obj.getClass()) return false; final TextEntity other = (TextEntity) obj; - if (entity == null) { - if (other.entity != null) + if (id == null) { + if (other.id != null) return false; - } else if (!entity.equals(other.entity)) + } else if (!id.equals(other.id)) return false; return true; } - @Override - public int compareTo(final TextEntity o) { - return count.compareTo(o.getCount()); - } - @Override public String toString() { - return "TextEntity [entity=" + entity + ", url=" + url + ", count=" + count + ", types=" + types + "]"; + return "TextEntity [id=" + id + ", url=" + url + ", types=" + types + "]"; } } diff --git a/vipra-util/src/main/java/de/vipra/util/model/TextEntityCount.java b/vipra-util/src/main/java/de/vipra/util/model/TextEntityCount.java new file mode 100644 index 00000000..7f16790e --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/model/TextEntityCount.java @@ -0,0 +1,37 @@ +package de.vipra.util.model; + +import java.io.Serializable; + +import org.mongodb.morphia.annotations.Embedded; + +@SuppressWarnings("serial") +@Embedded +public class TextEntityCount implements Comparable<TextEntityCount>, Serializable { + + @Embedded + private TextEntity entity; + + private Integer count; + + public TextEntity getEntity() { + return entity; + } + + public void setEntity(TextEntity entity) { + this.entity = entity; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + + @Override + public int compareTo(TextEntityCount o) { + return count.compareTo(o.getCount()); + } + +} diff --git a/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java b/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java new file mode 100644 index 00000000..8d20134f --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java @@ -0,0 +1,98 @@ +package de.vipra.util.model; + +import java.io.Serializable; +import java.util.List; + +import org.mongodb.morphia.annotations.Entity; +import org.mongodb.morphia.annotations.Id; +import org.mongodb.morphia.annotations.Reference; + +import de.vipra.util.an.QueryIgnore; + +@SuppressWarnings("serial") +@Entity(value = "textentities", noClassnameStored = true) +public class TextEntityFull implements Model<String>, Serializable { + + @Id + private String id; + + @Reference + @QueryIgnore(multi = true) + private TopicModel topicModel; + + @QueryIgnore(multi = true) + private String url; + + @QueryIgnore(multi = true) + private List<String> types; + + public TextEntityFull() {} + + public TextEntityFull(final TextEntity textEntity) { + this.id = textEntity.getId(); + this.url = textEntity.getUrl(); + this.types = textEntity.getTypes(); + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public TopicModel getTopicModel() { + return topicModel; + } + + public void setTopicModel(TopicModel topicModel) { + this.topicModel = topicModel; + } + + public String getUrl() { + return url; + } + + public void setUrl(final String url) { + this.url = url; + } + + public List<String> getTypes() { + return types; + } + + public void setTypes(final List<String> types) { + this.types = types; + } + + public String aTag() { + return "<a href=\"" + url + "\">" + id + "</a>"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final TextEntityFull other = (TextEntityFull) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} -- GitLab