udata 10.8.2.dev36809__py2.py3-none-any.whl → 10.8.2.dev36863__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- udata/core/metrics/helpers.py +10 -0
- udata/core/metrics/tasks.py +144 -1
- udata/static/chunks/{10.471164b2a9fe15614797.js → 10.8ca60413647062717b1e.js} +3 -3
- udata/static/chunks/{10.471164b2a9fe15614797.js.map → 10.8ca60413647062717b1e.js.map} +1 -1
- udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.b6f741fcc366abfad9c4.js} +3 -3
- udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.b6f741fcc366abfad9c4.js.map} +1 -1
- udata/static/chunks/{13.f29411b06be1883356a3.js → 13.2d06442dd9a05d9777b5.js} +2 -2
- udata/static/chunks/{13.f29411b06be1883356a3.js.map → 13.2d06442dd9a05d9777b5.js.map} +1 -1
- udata/static/chunks/{17.3bd0340930d4a314ce9c.js → 17.e8e4caaad5cb0cc0bacc.js} +2 -2
- udata/static/chunks/{17.3bd0340930d4a314ce9c.js.map → 17.e8e4caaad5cb0cc0bacc.js.map} +1 -1
- udata/static/chunks/{19.8da42e8359d72afc2618.js → 19.f03a102365af4315f9db.js} +3 -3
- udata/static/chunks/{19.8da42e8359d72afc2618.js.map → 19.f03a102365af4315f9db.js.map} +1 -1
- udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.778091d55cd8ea39af6b.js} +2 -2
- udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.778091d55cd8ea39af6b.js.map} +1 -1
- udata/static/chunks/{9.07515e5187f475bce828.js → 9.033d7e190ca9e226a5d0.js} +3 -3
- udata/static/chunks/{9.07515e5187f475bce828.js.map → 9.033d7e190ca9e226a5d0.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/metrics/__init__.py +0 -0
- udata/tests/metrics/conftest.py +15 -0
- udata/tests/metrics/helpers.py +58 -0
- udata/tests/metrics/test_metrics.py +67 -0
- udata/tests/metrics/test_tasks.py +171 -0
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +72 -65
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +72 -65
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +72 -65
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +72 -65
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +72 -65
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +72 -65
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +72 -65
- {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/METADATA +2 -1
- {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/RECORD +43 -38
- {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/LICENSE +0 -0
- {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/WHEEL +0 -0
- {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/entry_points.txt +0 -0
- {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/top_level.txt +0 -0
udata/core/metrics/helpers.py
CHANGED
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
from collections import OrderedDict
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
4
|
from typing import Dict, List, Union
|
|
5
|
+
from urllib.parse import urlencode
|
|
5
6
|
|
|
6
7
|
import requests
|
|
7
8
|
from bson import ObjectId
|
|
@@ -68,6 +69,15 @@ def get_metrics_for_model(
|
|
|
68
69
|
return [{} for _ in range(len(metrics_labels))]
|
|
69
70
|
|
|
70
71
|
|
|
72
|
+
def get_download_url(model: str, id: Union[str, ObjectId, None]) -> str:
|
|
73
|
+
api_namespace = model + "s" if model != "site" else model
|
|
74
|
+
base_url = f"{current_app.config['METRICS_API']}/{api_namespace}/data/csv/"
|
|
75
|
+
args = {"metric_month__sort": "asc"}
|
|
76
|
+
if id:
|
|
77
|
+
args[f"{model}_id__exact"] = id
|
|
78
|
+
return f"{base_url}?{urlencode(args)}"
|
|
79
|
+
|
|
80
|
+
|
|
71
81
|
def compute_monthly_aggregated_metrics(aggregation_res: CommandCursor) -> OrderedDict:
|
|
72
82
|
monthly_metrics = OrderedDict((month, 0) for month in get_last_13_months())
|
|
73
83
|
for monthly_count in aggregation_res:
|
udata/core/metrics/tasks.py
CHANGED
|
@@ -1,9 +1,152 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
|
|
6
|
+
import requests
|
|
1
7
|
from flask import current_app
|
|
2
8
|
|
|
9
|
+
from udata.core.dataservices.models import Dataservice
|
|
3
10
|
from udata.core.metrics.signals import on_site_metrics_computed
|
|
4
|
-
from udata.models import Site
|
|
11
|
+
from udata.models import CommunityResource, Dataset, Organization, Reuse, Site, db
|
|
5
12
|
from udata.tasks import job
|
|
6
13
|
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def log_timing(func):
|
|
18
|
+
@wraps(func)
|
|
19
|
+
def timeit_wrapper(*args, **kwargs):
|
|
20
|
+
# Better log if we're using Python 3.9
|
|
21
|
+
name = func.__name__
|
|
22
|
+
model = name.removeprefix("update_") if hasattr(name, "removeprefix") else name
|
|
23
|
+
|
|
24
|
+
log.info(f"Processing {model}…")
|
|
25
|
+
start_time = time.perf_counter()
|
|
26
|
+
result = func(*args, **kwargs)
|
|
27
|
+
total_time = time.perf_counter() - start_time
|
|
28
|
+
log.info(f"Done in {total_time:.4f} seconds.")
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
return timeit_wrapper
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def save_model(model: db.Document, model_id: str, metrics: Dict[str, int]) -> None:
|
|
35
|
+
try:
|
|
36
|
+
result = model.objects(id=model_id).update(
|
|
37
|
+
**{f"set__metrics__{key}": value for key, value in metrics.items()}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if result is None:
|
|
41
|
+
log.debug(f"{model.__name__} not found", extra={"id": model_id})
|
|
42
|
+
except Exception as e:
|
|
43
|
+
log.exception(e)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def iterate_on_metrics(target: str, value_keys: List[str], page_size: int = 50) -> dict:
|
|
47
|
+
"""
|
|
48
|
+
Yield all elements with not zero values for the keys inside `value_keys`.
|
|
49
|
+
If you pass ['visit', 'download_resource'], it will do a `OR` and get
|
|
50
|
+
metrics with one of the two values not zero.
|
|
51
|
+
"""
|
|
52
|
+
yielded = set()
|
|
53
|
+
|
|
54
|
+
for value_key in value_keys:
|
|
55
|
+
url = f"{current_app.config['METRICS_API']}/{target}_total/data/"
|
|
56
|
+
url += f"?{value_key}__greater=1&page_size={page_size}"
|
|
57
|
+
|
|
58
|
+
with requests.Session() as session:
|
|
59
|
+
while url is not None:
|
|
60
|
+
r = session.get(url, timeout=10)
|
|
61
|
+
r.raise_for_status()
|
|
62
|
+
data = r.json()
|
|
63
|
+
|
|
64
|
+
for row in data["data"]:
|
|
65
|
+
if row["__id"] not in yielded:
|
|
66
|
+
yielded.add(row["__id"])
|
|
67
|
+
yield row
|
|
68
|
+
|
|
69
|
+
url = data["links"].get("next")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@log_timing
|
|
73
|
+
def update_resources_and_community_resources():
|
|
74
|
+
for data in iterate_on_metrics("resources", ["download_resource"]):
|
|
75
|
+
if data["dataset_id"] is None:
|
|
76
|
+
save_model(
|
|
77
|
+
CommunityResource,
|
|
78
|
+
data["resource_id"],
|
|
79
|
+
{
|
|
80
|
+
"views": data["download_resource"],
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
Dataset.objects(resources__id=data["resource_id"]).update(
|
|
85
|
+
**{"set__resources__$__metrics__views": data["download_resource"]}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@log_timing
|
|
90
|
+
def update_datasets():
|
|
91
|
+
for data in iterate_on_metrics("datasets", ["visit", "download_resource"]):
|
|
92
|
+
save_model(
|
|
93
|
+
Dataset,
|
|
94
|
+
data["dataset_id"],
|
|
95
|
+
{
|
|
96
|
+
"views": data["visit"],
|
|
97
|
+
"resources_downloads": data["download_resource"],
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@log_timing
|
|
103
|
+
def update_dataservices():
|
|
104
|
+
for data in iterate_on_metrics("dataservices", ["visit"]):
|
|
105
|
+
save_model(
|
|
106
|
+
Dataservice,
|
|
107
|
+
data["dataservice_id"],
|
|
108
|
+
{
|
|
109
|
+
"views": data["visit"],
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@log_timing
|
|
115
|
+
def update_reuses():
|
|
116
|
+
for data in iterate_on_metrics("reuses", ["visit"]):
|
|
117
|
+
save_model(Reuse, data["reuse_id"], {"views": data["visit"]})
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@log_timing
|
|
121
|
+
def update_organizations():
|
|
122
|
+
# We're currently using visit_dataset as global metric for an orga
|
|
123
|
+
for data in iterate_on_metrics("organizations", ["visit_dataset"]):
|
|
124
|
+
save_model(
|
|
125
|
+
Organization,
|
|
126
|
+
data["organization_id"],
|
|
127
|
+
{
|
|
128
|
+
"views": data["visit_dataset"],
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def update_metrics_for_models():
|
|
134
|
+
log.info("Starting…")
|
|
135
|
+
update_datasets()
|
|
136
|
+
update_resources_and_community_resources()
|
|
137
|
+
update_dataservices()
|
|
138
|
+
update_reuses()
|
|
139
|
+
update_organizations()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@job("update-metrics", route="low.metrics")
|
|
143
|
+
def update_metrics(self):
|
|
144
|
+
"""Update udata objects metrics"""
|
|
145
|
+
if not current_app.config["METRICS_API"]:
|
|
146
|
+
log.error("You need to set METRICS_API to run update-metrics")
|
|
147
|
+
exit(1)
|
|
148
|
+
update_metrics_for_models()
|
|
149
|
+
|
|
7
150
|
|
|
8
151
|
@job("compute-site-metrics")
|
|
9
152
|
def compute_site_metrics(self):
|