From 2fe570554274b417662e0458e1a330c538445893 Mon Sep 17 00:00:00 2001 From: Viktor Lofgren Date: Tue, 16 Jan 2024 17:10:09 +0100 Subject: [PATCH] (control) GUI for ranking sets Still missing is some polish, forms don't have proper labels, validation is inconsistent, no error messages, etc. --- .../db/DomainRankingSetsService.java | 10 ++ .../nu/marginalia/control/ControlService.java | 2 + .../java/nu/marginalia/control/Redirects.java | 1 + .../svc/ControlDomainRankingSetsService.java | 98 +++++++++++++++++++ .../templates/control/partials/nav.hdb | 3 +- .../control/sys/domain-ranking-sets.hdb | 38 +++++++ .../control/sys/new-domain-ranking-set.hdb | 74 ++++++++++++++ .../control/sys/update-domain-ranking-set.hdb | 88 +++++++++++++++++ 8 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 code/services-core/control-service/src/main/java/nu/marginalia/control/sys/svc/ControlDomainRankingSetsService.java create mode 100644 code/services-core/control-service/src/main/resources/templates/control/sys/domain-ranking-sets.hdb create mode 100644 code/services-core/control-service/src/main/resources/templates/control/sys/new-domain-ranking-set.hdb create mode 100644 code/services-core/control-service/src/main/resources/templates/control/sys/update-domain-ranking-set.hdb diff --git a/code/common/db/src/main/java/nu/marginalia/db/DomainRankingSetsService.java b/code/common/db/src/main/java/nu/marginalia/db/DomainRankingSetsService.java index 6045cb76..a977e0de 100644 --- a/code/common/db/src/main/java/nu/marginalia/db/DomainRankingSetsService.java +++ b/code/common/db/src/main/java/nu/marginalia/db/DomainRankingSetsService.java @@ -63,6 +63,9 @@ public class DomainRankingSetsService { stmt.setInt(4, domainRankingSet.depth()); stmt.setString(5, domainRankingSet.definition()); stmt.executeUpdate(); + + if (!conn.getAutoCommit()) + conn.commit(); } catch (SQLException ex) { logger.error("Failed to update domain set", ex); @@ -78,6 +81,9 @@ public class DomainRankingSetsService { { stmt.setString(1, domainRankingSet.name()); stmt.executeUpdate(); + + if (!conn.getAutoCommit()) + conn.commit(); } catch (SQLException ex) { logger.error("Failed to delete domain set", ex); @@ -152,5 +158,9 @@ public class DomainRankingSetsService { .toArray(String[]::new); } + public boolean isSpecial() { + return algorithm() == DomainSetAlgorithm.SPECIAL; + } + } } diff --git a/code/services-core/control-service/src/main/java/nu/marginalia/control/ControlService.java b/code/services-core/control-service/src/main/java/nu/marginalia/control/ControlService.java index efe28e2f..c3a4e88f 100644 --- a/code/services-core/control-service/src/main/java/nu/marginalia/control/ControlService.java +++ b/code/services-core/control-service/src/main/java/nu/marginalia/control/ControlService.java @@ -53,6 +53,7 @@ public class ControlService extends Service { RandomExplorationService randomExplorationService, DataSetsService dataSetsService, ControlNodeService controlNodeService, + ControlDomainRankingSetsService controlDomainRankingSetsService, ControlActorService controlActorService ) throws IOException { @@ -66,6 +67,7 @@ public class ControlService extends Service { messageQueueService.register(); sysActionsService.register(); dataSetsService.register(); + controlDomainRankingSetsService.register(); // node controlFileStorageService.register(); diff --git a/code/services-core/control-service/src/main/java/nu/marginalia/control/Redirects.java b/code/services-core/control-service/src/main/java/nu/marginalia/control/Redirects.java index 2ade2779..7750b998 100644 --- a/code/services-core/control-service/src/main/java/nu/marginalia/control/Redirects.java +++ b/code/services-core/control-service/src/main/java/nu/marginalia/control/Redirects.java @@ -8,6 +8,7 @@ public class Redirects { public static final HtmlRedirect redirectToOverview = new HtmlRedirect("/"); public static final HtmlRedirect redirectToBlacklist = new HtmlRedirect("/blacklist"); public static final HtmlRedirect redirectToComplaints = new HtmlRedirect("/complaints"); + public static final HtmlRedirect redirectToRankingDataSets = new HtmlRedirect("/domain-ranking-sets"); public static final HtmlRedirect redirectToMessageQueue = new HtmlRedirect("/message-queue"); public static class HtmlRedirect implements ResponseTransformer { diff --git a/code/services-core/control-service/src/main/java/nu/marginalia/control/sys/svc/ControlDomainRankingSetsService.java b/code/services-core/control-service/src/main/java/nu/marginalia/control/sys/svc/ControlDomainRankingSetsService.java new file mode 100644 index 00000000..362ef0f8 --- /dev/null +++ b/code/services-core/control-service/src/main/java/nu/marginalia/control/sys/svc/ControlDomainRankingSetsService.java @@ -0,0 +1,98 @@ +package nu.marginalia.control.sys.svc; + +import com.google.inject.Inject; +import com.zaxxer.hikari.HikariDataSource; +import nu.marginalia.control.ControlRendererFactory; +import nu.marginalia.control.Redirects; +import nu.marginalia.db.DomainRankingSetsService; +import spark.Request; +import spark.Response; +import spark.Spark; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Map; + +public class ControlDomainRankingSetsService { + private final HikariDataSource dataSource; + private final ControlRendererFactory rendererFactory; + private final DomainRankingSetsService domainRankingSetsService; + + @Inject + public ControlDomainRankingSetsService(HikariDataSource dataSource, + ControlRendererFactory rendererFactory, + DomainRankingSetsService domainRankingSetsService) { + this.dataSource = dataSource; + this.rendererFactory = rendererFactory; + this.domainRankingSetsService = domainRankingSetsService; + } + + public void register() throws IOException { + var datasetsRenderer = rendererFactory.renderer("control/sys/domain-ranking-sets"); + var updateDatasetRenderer = rendererFactory.renderer("control/sys/update-domain-ranking-set"); + var newDatasetRenderer = rendererFactory.renderer("control/sys/new-domain-ranking-set"); + + Spark.get("/public/domain-ranking-sets", this::rankingSetsModel, datasetsRenderer::render); + Spark.get("/public/domain-ranking-sets/new", (rq,rs) -> new Object(), newDatasetRenderer::render); + Spark.get("/public/domain-ranking-sets/:id", this::rankingSetModel, updateDatasetRenderer::render); + Spark.post("/public/domain-ranking-sets/:id", this::alterSetModel, Redirects.redirectToRankingDataSets); + } + + private Object alterSetModel(Request request, Response response) throws SQLException { + final String act = request.queryParams("act"); + final String id = request.params("id"); + if ("update".equals(act)) { + domainRankingSetsService.upsert(new DomainRankingSetsService.DomainRankingSet( + id, + request.queryParams("description"), + DomainRankingSetsService.DomainSetAlgorithm.valueOf(request.queryParams("algorithm")), + Integer.parseInt(request.queryParams("depth")), + request.queryParams("definition") + )); + return ""; + } + else if ("delete".equals(act)) { + var model = domainRankingSetsService.get(id).orElseThrow(); + if (model.isSpecial()) { + throw new IllegalArgumentException("Cannot delete special ranking set"); + } + domainRankingSetsService.delete(model); + return ""; + } + else if ("create".equals(act)) { + if (domainRankingSetsService.get(request.queryParams("name")).isPresent()) { + throw new IllegalArgumentException("Ranking set with that name already exists"); + } + + domainRankingSetsService.upsert(new DomainRankingSetsService.DomainRankingSet( + request.queryParams("name"), + request.queryParams("description"), + DomainRankingSetsService.DomainSetAlgorithm.valueOf(request.queryParams("algorithm")), + Integer.parseInt(request.queryParams("depth")), + request.queryParams("definition") + )); + return ""; + } + + throw new UnsupportedOperationException(); + } + + private Object rankingSetsModel(Request request, Response response) { + return Map.of("rankingSets", domainRankingSetsService.getAll()); + } + private Object rankingSetModel(Request request, Response response) throws SQLException { + var model = domainRankingSetsService.get(request.params("id")).orElseThrow(); + return Map.of("rankingSet", model, + "selectedAlgo", Map.of( + "special", model.algorithm() == DomainRankingSetsService.DomainSetAlgorithm.SPECIAL, + "adjacency_cheirank", model.algorithm() == DomainRankingSetsService.DomainSetAlgorithm.ADJACENCY_CHEIRANK, + "adjacency_pagerank", model.algorithm() == DomainRankingSetsService.DomainSetAlgorithm.ADJACENCY_PAGERANK, + "links_cheirank", model.algorithm() == DomainRankingSetsService.DomainSetAlgorithm.LINKS_CHEIRANK, + "links_pagerank", model.algorithm() == DomainRankingSetsService.DomainSetAlgorithm.LINKS_PAGERANK) + ); + + + + + } +} diff --git a/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb b/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb index c72fafae..9cadd3be 100644 --- a/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb +++ b/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb @@ -34,7 +34,8 @@ diff --git a/code/services-core/control-service/src/main/resources/templates/control/sys/domain-ranking-sets.hdb b/code/services-core/control-service/src/main/resources/templates/control/sys/domain-ranking-sets.hdb new file mode 100644 index 00000000..79fcfb32 --- /dev/null +++ b/code/services-core/control-service/src/main/resources/templates/control/sys/domain-ranking-sets.hdb @@ -0,0 +1,38 @@ + + + + Control Service + {{> control/partials/head-includes }} + + +{{> control/partials/nav}} +
+

Domain Ranking Sets

+
+ Domain ranking sets configure the ranking algorithms used to determine the importance of a domain. +
+ + + + + + + + + {{#each rankingSets}} + + + + + + + {{/each}} +
NameDescriptionAlgorithmDepth
{{name}}{{description}}{{algorithm}}{{depth}}
+ + +
+ +{{> control/partials/foot-includes }} + \ No newline at end of file diff --git a/code/services-core/control-service/src/main/resources/templates/control/sys/new-domain-ranking-set.hdb b/code/services-core/control-service/src/main/resources/templates/control/sys/new-domain-ranking-set.hdb new file mode 100644 index 00000000..7c086276 --- /dev/null +++ b/code/services-core/control-service/src/main/resources/templates/control/sys/new-domain-ranking-set.hdb @@ -0,0 +1,74 @@ + + + + Control Service + {{> control/partials/head-includes }} + + +{{> control/partials/nav}} +
+

Create Domain Ranking Set

+
+ + + + + + + + + + + + + + + + + + + +
Name + +
+ The name is how the ranking set is identified in the query parameters, + and also decides the file name of the persisted ranking set definition. Keep it simple. +
+
Algorithm + +
+ + The algorithm used to rank the domains. The LINKS algorithms use the link graph, and the ADJACENCY + algorithms use the adjacency graph. CheiRank is a variant of PageRank that uses the reversed graph. + +
+
Description + +
+ This is purely to help keep track of what this ranking set does. +
+
Depth + +
+ Up to this number of domains are ranked, the rest are excluded. +
+
Definition
+ +
+ A list of domain names, one per line, possibly globbed with SQL-style '%' wildcards. + These are used as the origin point for the Personalized PageRank algorithm, and will be considered + the central points of the link or adjacency graph. If no domains are specified, the entire domain space is used, as per the PageRank paper. + +
+
+ +
+
+ +{{> control/partials/foot-includes }} + \ No newline at end of file diff --git a/code/services-core/control-service/src/main/resources/templates/control/sys/update-domain-ranking-set.hdb b/code/services-core/control-service/src/main/resources/templates/control/sys/update-domain-ranking-set.hdb new file mode 100644 index 00000000..3e228c67 --- /dev/null +++ b/code/services-core/control-service/src/main/resources/templates/control/sys/update-domain-ranking-set.hdb @@ -0,0 +1,88 @@ + + + + Control Service + {{> control/partials/head-includes }} + + +{{> control/partials/nav}} +
+{{#with rankingSet}} +

Domain Ranking Set: {{name}}

+
+ + + + + + + + + + + + + + + + + + + +
Name + {{#if special}}{{/if}} + +
+ The name is how the ranking set is identified in the query parameters, + and also decides the file name of the persisted ranking set definition. Keep it simple. +
+
Algorithm + {{#if special}}{{/if}} + +
+ + The algorithm used to rank the domains. The LINKS algorithms use the link graph, and the ADJACENCY + algorithms use the adjacency graph. CheiRank is a variant of PageRank that uses the reversed graph. + +
+
Description + {{#if special}}{{/if}} + +
+ This is purely to help keep track of what this ranking set does. +
+
Depth + +
+ Up to this number of domains are ranked, the rest are excluded. +
+
Definition
+ +
+ A list of domain names, one per line, possibly globbed with SQL-style '%' wildcards. + These are used as the origin point for the Personalized PageRank algorithm, and will be considered + the central points of the link or adjacency graph. If no domains are specified, the entire domain space is used, as per the PageRank paper. + +
+
+ +
+
+ + + + + +{{/with}} +
+ +{{> control/partials/foot-includes }} + \ No newline at end of file