]> git.ipfire.org Git - ipfire.org.git/commitdiff
analytics: Add page about docs analytics
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 8 Jan 2024 17:45:41 +0000 (17:45 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Mon, 8 Jan 2024 17:45:41 +0000 (17:45 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/analytics.py
src/backend/wiki.py
src/templates/analytics/docs.html [new file with mode: 0644]
src/templates/analytics/index.html
src/web/__init__.py
src/web/analytics.py

index 3856bdacd28798f9b2b59da311e667f90e635470..2df8b3a0598b31e9f5785a77d0df39ef419624a0 100644 (file)
@@ -116,6 +116,7 @@ templates_DATA = \
 templatesdir = $(datadir)/templates
 
 templates_analytics_DATA = \
+       src/templates/analytics/docs.html \
        src/templates/analytics/index.html
 
 templates_analyticsdir = $(templatesdir)/analytics
index ac35013038de82169568415f41b0d00439032db7..274181aeb775bbf3aaaad2c0918f30442799d3a9 100644 (file)
@@ -158,3 +158,63 @@ class Analytics(misc.Object):
                        return res.c
 
                return 0
+
+       # Popular Pages
+
+       def get_most_popular_docs_pages(self, host, since=None, offset=None, limit=None):
+               # Make since an absolute timestamp
+               if since and isinstance(since, datetime.timedelta):
+                       since = datetime.datetime.utcnow() - since
+
+               pages = self.backend.wiki._get_pages("""
+                       SELECT
+                               wiki.*,
+                               COUNT(*) AS _c
+                       FROM
+                               wiki_current
+                       LEFT JOIN
+                               wiki ON wiki_current.id = wiki.id
+                       LEFT JOIN
+                               analytics_unique_visits
+                                       ON (CASE WHEN wiki.page = '/' THEN '/docs'
+                                               ELSE '/docs' || wiki.page END) = analytics_unique_visits.uri
+                       WHERE
+                               host = %s
+                       AND
+                               uri LIKE '/docs%%'
+                       GROUP BY
+                               wiki.id
+                       ORDER BY
+                               _c DESC
+                       LIMIT
+                               %s
+                       OFFSET
+                               %s
+                       """, host, limit, offset,
+               )
+
+               return list(pages)
+
+       # Search
+
+       def get_search_queries(self, host, uri, limit=None):
+               res = self.db.query("""
+                       SELECT
+                               q,
+                               COUNT(*) AS c
+                       FROM
+                               analytics_unique_visits
+                       WHERE
+                               host = %s
+                       AND
+                               uri = %s
+                       AND
+                               q IS NOT NULL
+                       GROUP BY
+                               q
+                       LIMIT
+                               %s
+                       """, host, uri, limit,
+               )
+
+               return { " ".join(row.q) : row.c for row in res }
index caacf702cc4476c6e458d4e59cd7a642846c7c6f..748934359c00f9d0127d30657d8283075d01c99c 100644 (file)
@@ -390,6 +390,9 @@ class Page(misc.Object):
 
                return NotImplemented
 
+       def __hash__(self):
+               return hash(self.page)
+
        @staticmethod
        def sanitise_page_name(page):
                if not page:
diff --git a/src/templates/analytics/docs.html b/src/templates/analytics/docs.html
new file mode 100644 (file)
index 0000000..cb5b0b1
--- /dev/null
@@ -0,0 +1,59 @@
+{% extends "../base.html" %}
+
+{% block title %}{{ _("Analytics") }} - {{ _("Docs") }}{% end block %}
+
+{% block container %}
+       <section class="hero is-primary">
+               <div class="hero-body">
+                       <div class="container">
+                               <nav class="breadcrumb" aria-label="breadcrumbs">
+                                       <ul>
+                                               <li>
+                                                       <a href="/">{{ _("Home") }}</a>
+                                               </li>
+                                               <li>
+                                                       <a href="/analytics">{{ _("Analytics") }}</a>
+                                               </li>
+                                               <li class="is-active">
+                                                       <a href="#" aria-current="page">{{ _("Documentation") }}</a>
+                                               </li>
+                                       </ul>
+                               </nav>
+
+                               <h1 class="title">{{ _("Documents") }}</h1>
+                       </div>
+               </div>
+       </section>
+
+       <section class="section">
+               <div class="container">
+                       <h4 class="title is-4">{{ _("Most Popular Pages") }}</h4>
+
+                       {% module DocsList(popular_pages, show_author=False) %}
+               </div>
+       </section>
+
+       <section class="section">
+               <div class="container">
+                       <h4 class="title is-4">{{ _("Popular Searches") }}</h4>
+
+                       <table class="table is-fullwidth">
+                               <thead>
+                                       <tr>
+                                               <th>{{ _("Search Query") }}</th>
+                                               <th class="has-text-right">{{ _("Searches") }}</th>
+                                       </tr>
+                               </thead>
+
+                               <tbody>
+                                       {% for search in sorted(popular_searches, key=lambda q: popular_searches[q], reverse=True) %}
+                                               <tr>
+                                                       <th scope="row">{{ search }}</th>
+                                                       <td class="has-text-right">{{ popular_searches[search] }}</td>
+                                               </tr>
+                                       {% end %}
+                               </tbody>
+                       </table>
+               </div>
+       </section>
+{% end block %}
index 8cd60afb1f8339b642798a1887be85c3691fb2a4..7f3802297a16378f4a2700072c563abb321c0059 100644 (file)
                        </div>
                </div>
        </section>
+
+       <section class="section">
+               <div class="container">
+                       <div class="buttons">
+                               <a class="button" href="/analytics/docs">
+                                       {{ _("Documentation") }}
+                               </a>
+                       </div>
+               </div>
+       </section>
 {% end block %}
index 59429c0d419285b42c24404d6e1af0e2792edc54..ded884c1ba96f17dd800f3469b83e91dbd017c91 100644 (file)
@@ -124,6 +124,7 @@ class Application(tornado.web.Application):
 
                        # Analytics
                        (r"/analytics", analytics.IndexHandler),
+                       (r"/analytics/docs", analytics.DocsHandler),
 
                        # Authentication
                        (r"/join", auth.JoinHandler),
index ab8179e5704d35317589d0987c602eb5f29c4209..8eafe488235343657a0aa287b756963bd1c0ee75 100644 (file)
@@ -12,6 +12,21 @@ class IndexHandler(base.BaseHandler):
                self.render("analytics/index.html")
 
 
+class DocsHandler(base.BaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               # Most Popular Pages
+               popular_pages = self.backend.analytics.get_most_popular_docs_pages(
+                       self.request.host, since=datetime.timedelta(hours=24 * 365), limit=50)
+
+               # Popular Searches
+               popular_searches = self.backend.analytics.get_search_queries(
+                       self.request.host, "/docs/search", limit=25)
+
+               self.render("analytics/docs.html",
+                       popular_pages=popular_pages, popular_searches=popular_searches)
+
+
 class SummaryModule(ui_modules.UIModule):
        def render(self, host=None, uri=None):
                if host is None: