Skip to content

Commit

Permalink
Add a plausible api class
Browse files Browse the repository at this point in the history
  • Loading branch information
Tschuppi81 committed Jan 28, 2025
1 parent dc72178 commit 83813d8
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 87 deletions.
Empty file.
111 changes: 111 additions & 0 deletions src/onegov/plausible/plausible_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from __future__ import annotations
import logging
from typing import Any

import requests
from requests import HTTPError

log = logging.getLogger('onegov.plausible')


class PlausibleAPI:

def __init__(self) -> None:
"""
Initialize Plausible API client
For details about the API see https://plausible.io/docs/stats-api
"""
self.url = 'https://analytics.seantis.ch/api/v2/query'

# api key generated https://analytics.seantis.ch/settings/api-keys
api_key = (
'eR9snr0RzrglMLrKqVPNQ_IYL3dD6hyOX0-2gyRMlxSSSTk5bg6NjORWtbNEMoHU')

self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
self.site_id = 'wil.onegovcloud.ch' # configure it, may be in web
# statistics settings

def _send_request(self, payload: dict[str, Any]) -> dict[Any, Any]:
"""
Send request to Plausible API
"""
try:
response = requests.post(
self.url, headers=self.headers, json=payload, timeout=30)
response.raise_for_status()
except HTTPError as http_err:
if response.status_code == 401:
log.error('Unauthorized: Invalid API key or insufficient '
'permissions', exc_info=http_err)
else:
log.error('HTTP error occurred', exc_info=http_err)
except Exception as err:
log.error('An error occurred', exc_info=err)

return response.json()

def get_stats(self) -> dict[str, int] | dict[str, str]:
"""
Get basic stats from Plausible API
"""
figures = ['-', '-', '-', '-']
metrics = [
'visitors', 'pageviews', 'views_per_visit', 'visit_duration']
date_range = '7d'

payload = {
'site_id': self.site_id,
'metrics': metrics,
'date_range': date_range,
'filters': [],
'dimensions': []
}

r = self._send_request(payload)

results = r.get('results', [])
figures = results[0].get('metrics', figures)

texts = [
'Unique Visitors in the Last Week',
'Total Page Views in the Last Week',
'Number of Page Views per Visit',
'Average Visit Duration in Seconds'
]

return {text: figures[i] for i, text in enumerate(texts)}

def get_top_pages(self, limit: int = 10) -> dict[str, int]:
"""
Get top pages from Plausible API
"""
metrics = ['visitors']
date_range = '7d'
dimensions = ['event:page']

payload = {
'site_id': self.site_id,
'metrics': metrics,
'date_range': date_range,
'dimensions': dimensions,
'pagination': {
'limit': limit
}
}

r = self._send_request(payload)

results = r.get('results', [])
if not results:
return {}

return {
result['dimensions'][0]: result['metrics'][0] for result in results
}
105 changes: 18 additions & 87 deletions src/onegov/town6/boardlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
from datetime import timedelta
from functools import cached_property

import requests
from requests import HTTPError
from sedate import utcnow
from typing import TYPE_CHECKING

from onegov.org.layout import DefaultLayout
from onegov.org.models import Boardlet, BoardletFact, News
from onegov.page import Page
from onegov.plausible.plausible_api import PlausibleAPI
from onegov.ticket import Ticket
from onegov.town6 import TownApp, _

Check warning on line 14 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L13-L14

Added lines #L13 - L14 were not covered by tests

Expand Down Expand Up @@ -103,50 +102,6 @@ def get_icon_title(request: TownRequest, visibility: str) -> str:
mapping={'visibility': visibility}))


def plausible_stats(
metrics: list[str],
date_range: str,
filters: list[str] | None = None,
dimensions: list[str] | None = None
) -> dict[str, list[dict[str, str]]]:
api_key = (
'eR9snr0RzrglMLrKqVPNQ_IYL3dD6hyOX0-2gyRMlxSSSTk5bg6NjORWtbNEMoHU')
site_id = 'wil.onegovcloud.ch'

if filters is None:
filters = []
if dimensions is None:
dimensions = []

url = 'https://analytics.seantis.ch/api/v2/query'
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
data = {
'site_id': site_id,
'metrics': metrics,
'date_range': date_range,
'filters': filters,
'dimensions': dimensions
}

try:
response = requests.post(url, headers=headers, json=data, timeout=30)
response.raise_for_status() # Raise an error for bad status codes
except HTTPError as e:
if response.status_code == 401:
print('Unauthorized: Invalid API key or insufficient '
'permissions', e)
else:
print('HTTP error', e)
except Exception as e:
print('Plausible error occurred:', e)

print('*** tschupre get plausible stats response', response.json())
return response.json()


@TownApp.boardlet(name='pages', order=(1, 2), icon='fa-edit')
class EditedPagesBoardlet(TownBoardlet):

Check warning on line 106 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L105-L106

Added lines #L105 - L106 were not covered by tests

Expand All @@ -159,6 +114,7 @@ def facts(self) -> Iterator[BoardletFact]:

last_edited_pages = self.session.query(Page).order_by(

Check warning on line 115 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L115

Added line #L115 was not covered by tests
Page.last_change.desc()).limit(8)

for p in last_edited_pages:
yield BoardletFact(

Check warning on line 119 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L118-L119

Added lines #L118 - L119 were not covered by tests
text='',
Expand All @@ -179,6 +135,7 @@ def title(self) -> str:
def facts(self) -> Iterator[BoardletFact]:
last_edited_news = self.session.query(News).order_by(

Check warning on line 136 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L134-L136

Added lines #L134 - L136 were not covered by tests
Page.last_change.desc()).limit(8)

for n in last_edited_news:
yield BoardletFact(

Check warning on line 140 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L139-L140

Added lines #L139 - L140 were not covered by tests
text='',
Expand All @@ -198,33 +155,13 @@ def title(self) -> str:
@property
def facts(self) -> Iterator[BoardletFact]:

Check warning on line 156 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L155-L156

Added lines #L155 - L156 were not covered by tests

data = plausible_stats(
['visitors', 'pageviews', 'views_per_visit',
'visit_duration'],
'7d',
)

values = data['results'][0]['metrics']

yield BoardletFact(
text='Unique Visitors in the Last Week',
number=values[0] or '-',
)

yield BoardletFact(
text='Total Page Views in the Last Week',
number=values[1] or '-',
)
results = PlausibleAPI().get_stats()

Check warning on line 158 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L158

Added line #L158 was not covered by tests

yield BoardletFact(
text='Number of Page Views per Visit',
number=values[2] or '-',
)

yield BoardletFact(
text='Average Visit Duration in Seconds',
number=values[3] or '-',
)
for text, number in results.items():
yield BoardletFact(

Check warning on line 161 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L160-L161

Added lines #L160 - L161 were not covered by tests
text=text,
number=number
)


@TownApp.boardlet(name='Top Pages', order=(2, 2))
Expand All @@ -237,22 +174,16 @@ def title(self) -> str:
@property
def facts(self) -> Iterator[BoardletFact]:

Check warning on line 175 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L174-L175

Added lines #L174 - L175 were not covered by tests

data = plausible_stats(
['visitors'],
'7d',
[],
['event:page']
)
results = PlausibleAPI().get_top_pages(limit=10)

Check warning on line 177 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L177

Added line #L177 was not covered by tests

# Extract and sort the results by the number of visits (metrics)
sorted_results = sorted(
data['results'], key=lambda x: x['metrics'][0], reverse=True)
if not results:
yield BoardletFact(

Check warning on line 180 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L179-L180

Added lines #L179 - L180 were not covered by tests
text=_('No data available'),
number=None
)

# Print the sorted results
for result in sorted_results[:10]:
print(f"Top Page: {result['dimensions'][0]}, Visits:"
f" {result['metrics'][0]}")
for text, number in results.items():
yield BoardletFact(

Check warning on line 186 in src/onegov/town6/boardlets.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/town6/boardlets.py#L185-L186

Added lines #L185 - L186 were not covered by tests
text=result['dimensions'][0],
number=result['metrics'][0] or '-',
text=text,
number=number
)

0 comments on commit 83813d8

Please sign in to comment.