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

Show current anniversaries on index page (RPB-46) #371

Merged
merged 2 commits into from
Jan 15, 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
147 changes: 139 additions & 8 deletions app/controllers/HomeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
Expand All @@ -37,6 +43,7 @@
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
Expand Down Expand Up @@ -101,8 +108,18 @@ public HomeController(WSClient httpClient) {
@Inject
IndexComponent index;

private Map<String, List<String>> anniversaries = new HashMap<>();

public static final Config CONFIG = ConfigFactory.load();

private static final List<Integer> ROUND_BIRTH = Arrays.asList(50, 70, 75, 100, 125, 150, 200,
250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1100,
1200, 1250, 1300, 1400, 1500, 1600, 1700, 1750, 1800, 1900, 2000);

private static final List<Integer> ROUND_DEATH = Arrays.asList(10, 25, 50, 75, 100, 125, 150,
200, 250, 300, 350, 400, 450, 500, 600, 700, 750, 800, 900, 950, 1000, 1100, 1200, 1250,
1300, 1400, 1500, 1600, 1700, 1750, 1800, 1900, 2000, 2100, 2200);

public static String config(String id) {
return CONFIG.getString(id);
}
Expand All @@ -125,19 +142,133 @@ public Result index() {
String queryString = String.format("depiction:* AND NOT gndIdentifier:(%s)",
CONFIG.getStringList("dontShowOnMainPage").stream()
.collect(Collectors.joining(" OR ")));
QueryStringQueryBuilder query = index.queryStringQuery(queryString);
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(query,
ScoreFunctionBuilders.randomFunction(System.currentTimeMillis()));
SearchRequestBuilder requestBuilder = index.client().prepareSearch(config("index.prod.name"))
.setQuery(functionScoreQuery).setFrom(0).setSize(1);
SearchHits hits = requestBuilder.execute().actionGet().getHits();
QueryBuilder query = randomScoreQuery(index.queryStringQuery(queryString));
SearchHits hits = prepareSearch().setQuery(query).setFrom(0).setSize(1).execute()
.actionGet().getHits();
AuthorityResource entity = null;
if (hits.getTotalHits() > 0) {
SearchHit hit = hits.getAt(0);
entity = entityWithImage(hit.getSourceAsString());
}
JsonNode dataset = Json.parse(readFile(config("dataset.file")));
return ok(views.html.index.render(entity, dataset, allHits()));
return ok(views.html.index.render(entity, dataset, allAnniversaries(), allHits()));
}

private List<String> allAnniversaries() {
// starting with any anniversaries on this day, fill up
// (to 5 overall) with random anniversaries from this month,
// retaining order (today first) and avoiding duplicates:
int fullSize = 5;
List<String> anniversaries = anniversariesFor(thisDay());
if (anniversaries.size() < fullSize) {
List<String> thisMonth = anniversariesFor(thisMonth());
for (int i = 0; anniversaries.size() < fullSize && i < thisMonth.size(); i++) {
if (!anniversaries.contains(thisMonth.get(i))) {
anniversaries.add(thisMonth.get(i));
}
}
}
return anniversaries;
}

private List<String> anniversariesFor(String datePattern) {
// don't query on each reload...
List<String> result = anniversaries.computeIfAbsent(datePattern, list -> {
QueryBuilder query = randomScoreQuery(anniversaryQuery(datePattern));
SearchHits hits = prepareSearch().setQuery(query).setFrom(0).setSize((int) allHits())
.execute().actionGet().getHits();
return Arrays.asList(hits.getHits()).stream().filter(hit -> hasRound(datePattern, hit))
.map(hit -> hitToAnniversary(datePattern, hit)).collect(Collectors.toList());
});
// ...but provide new selection/subset
Collections.shuffle(result);
return result.stream().limit(5).collect(Collectors.toList());

}

private boolean hasRound(String datePattern, SearchHit hit) {
JsonNode json = Json.parse(hit.getSourceAsString());
JsonNode birthNode = json.get("dateOfBirth");
JsonNode deathNode = json.get("dateOfDeath");
return isRound(birthNode, ROUND_BIRTH, datePattern)
|| isRound(deathNode, ROUND_DEATH, datePattern);
}

private boolean isRound(JsonNode node, List<Integer> round, String datePattern) {
return node != null && is(datePattern, node.get(0).asText(), round);
}

private FunctionScoreQueryBuilder randomScoreQuery(QueryBuilder query) {
return QueryBuilders.functionScoreQuery(query,
ScoreFunctionBuilders.randomFunction(System.currentTimeMillis()));
}

private String hitToAnniversary(String datePattern, SearchHit hit) {
JsonNode fullJson = Json.parse("[" + hit.getSourceAsString() + "]");
JsonNode suggestion = Json
.parse(toSuggestions(fullJson,
"preferredName,dateOfBirth-dateOfDeath,professionOrOccupation"))
.elements().next();
JsonNode json = Json.toJson(ImmutableMap.of( //
"label", suggestion.get("label").asText(), //
"url", suggestion.get("id").asText().replace(AuthorityResource.GND_PREFIX, "/"), //
"details", details(datePattern, fullJson)));
return Json
.stringify(json);
}

private String details(String datePattern, JsonNode json) {
String details = "";
JsonNode dateOfBirth = json.findValue("dateOfBirth");
String date;
if (dateOfBirth != null
&& is(datePattern, date = dateOfBirth.get(0).asText(), ROUND_BIRTH)) {
details = String.format("%s. Geburtstag, geb. am %s", //
yearsSince(date), AuthorityResource.germanDate(date));
} else if (is(datePattern, date = json.findValue("dateOfDeath").get(0).asText(), ROUND_DEATH)) {
details = String.format("%s. Todestag, gest. am %s", //
yearsSince(date), AuthorityResource.germanDate(date));
}
return details;
}

private boolean is(String datePattern, String date, List<Integer> round) {
return date.contains(datePattern.replace("*", ""))
&& round.contains((int) yearsSince(date));
}

private SearchRequestBuilder prepareSearch() {
return index.client().prepareSearch(config("index.prod.name"));
}

private QueryStringQueryBuilder anniversaryQuery(String today) {
String queryString = String.format(
"(dateOfBirth:(%s) OR dateOfDeath:(%s)) AND NOT gndIdentifier:(%s) AND _exists_:dateOfDeath",
today, today,
CONFIG.getStringList("dontShowOnMainPage").stream()
.collect(Collectors.joining(" OR ")));
QueryStringQueryBuilder query = index.queryStringQuery(queryString);
return query;
}

private long yearsSince(String date) {
try {
int year = Integer.parseInt(AuthorityResource.cleanDate(date).substring(0, 4));
return ChronoUnit.YEARS.between( //
LocalDate.of(year, 1, 1), LocalDate.of(ZonedDateTime.now().getYear(), 1, 1));
} catch (DateTimeParseException e) {
e.printStackTrace();
return -1;
}
}

public static String thisDay() {
return ZonedDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("\\d{4}",
"*"); // e.g return "*-01-09";
}

private static String thisMonth() {
return thisDay().replaceAll("\\d{2}$", "*"); // e.g. return "*-01-*";
}

/**
Expand Down Expand Up @@ -408,7 +539,7 @@ private Result jsonLines(String q, String filter, SearchResponse response) {
query = query.filter(index.queryStringQuery(filter));
}
TimeValue keepAlive = new TimeValue(60000);
SearchRequestBuilder scrollRequest = index.client().prepareSearch(config("index.prod.name"))
SearchRequestBuilder scrollRequest = prepareSearch()
.addSort(FieldSortBuilder.DOC_FIELD_NAME, SortOrder.ASC).setScroll(keepAlive).setQuery(query)
.setSize(100 /* hits per shard for each scroll */);
Logger.debug("Scrolling with query: q={}, request={}", q, scrollRequest);
Expand Down
18 changes: 15 additions & 3 deletions app/models/AuthorityResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -491,9 +492,20 @@ private String process(String field, String value, String label, int i, int size
return withDefaultHidden(field, size, i, result);
}

private String germanDate(String value) {
return LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse(value))
.format(DateTimeFormatter.ofPattern("dd.MM.uuuu", Locale.GERMAN));
public static String germanDate(String value) {
try {
return LocalDate
.from(DateTimeFormatter.ISO_LOCAL_DATE.parse(cleanDate(value)))
.format(DateTimeFormatter.ofPattern("dd.MM.uuuu", Locale.GERMAN));
} catch (DateTimeParseException e) {
e.printStackTrace();
return value;
}

}

public static String cleanDate(String value) {
return value.replaceAll(".*(\\d{4}-\\d{2}-\\d{2}).*", "$1");
}

private String withDefaultHidden(String field, int size, int i, String result) {
Expand Down
42 changes: 30 additions & 12 deletions app/views/index.scala.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
@* Copyright 2015-2018 Fabian Steeg, hbz. Licensed under the EPL 2.0 *@

@(entity: AuthorityResource, dataset: com.fasterxml.jackson.databind.JsonNode, allHits: Long)
@(entity: AuthorityResource, dataset: com.fasterxml.jackson.databind.JsonNode, anniversaries: List[String], allHits: Long)

@import helper._
@import controllers.HomeController.formatCount
@import controllers.HomeController.thisDay
@import play.api.libs.json._
@import controllers.HomeController.CONFIG

@main("", "RPPD", allHits) {
<div class="page-header">
<img class="media-object nrw-logo pull-right" src="@controllers.routes.Assets.versioned("images/wappen.png")" alt="NRW">
<h1>@dataset.get("alternateName").get("de").asText()<br/><small>@formatCount(allHits) Personen aus allen Wissensgebieten <span class="badge">beta</span></small></h1>
</div>
<div class="row">
<div class="col-md-@if(entity!=null){9}else{12} intro">
<p>
@Html(dataset.get("description").get("de").asText())
</p>
</div>
@if(entity!=null){<div class="col-md-3">
<figure>
<a href='@routes.HomeController.authority(entity.getId)'><img width="200px" id="index-image" src='https://lobid.org/[email protected]' alt="Darstellung von @entity.preferredName"/></a>
<figcaption><a href='@routes.HomeController.authority(entity.getId)'>@entity.preferredName</a><br/>@if(entity.imageAttribution!=null){<small>(@Html(entity.imageAttribution))</small>}</figcaption>
</figure>
</div>}
<div class="col-md-3">
<p><strong>Jubiläen</strong></p>
@for(resource <- anniversaries) {
<div>
<small>
@for(json <- Json.parse(resource).asOpt[JsValue];
label <- (json \ "label").asOpt[String];
details <- (json \ "details").asOpt[String];
url = (json \ "url").asOpt[String]) {
<a href="@url">@label</a>
<p>@details</p>
}
</small>
</div>
}
<p><a href='@routes.HomeController.search(date=thisDay)'>Aktuelle Jahrestage</a></p>
</div>
<div class="col-md-@if(entity!=null){6}else{9} intro">
@Html(dataset.get("description").get("de").asText())
</div>
@if(entity!=null){<div class="col-md-3">
<figure>
<a href='@routes.HomeController.authority(entity.getId)'><img width="200px" id="index-image" src='https://lobid.org/[email protected]' alt="Darstellung von @entity.preferredName"/></a>
<figcaption><a href='@routes.HomeController.authority(entity.getId)'>@entity.preferredName</a><br/>@if(entity.imageAttribution!=null){<small>(@Html(entity.imageAttribution))</small>}</figcaption>
</figure>
</div>}
</div>
<script type="application/ld+json">
{
Expand Down
2 changes: 1 addition & 1 deletion public/stylesheets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ h1 small .label {
figure {
display: table;
float: right;
margin-top: 10px;
margin-top: 5px;
}

figcaption {
Expand Down
1 change: 1 addition & 0 deletions transformAndIndexRppd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ cd ../rpb
bash transformRppd.sh
cd -
sbt "runMain apps.Index baseline"
curl -S -s -o /dev/null https://rppd.lobid.org
Loading