Skip to content

Commit 9a261b6

Browse files
authored
Merge pull request #50 from OpenSourceBrain/main
Latest main
2 parents aaf2544 + 5fc2990 commit 9a261b6

18 files changed

Lines changed: 616 additions & 155 deletions
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Enter one line description here.
4+
5+
File:
6+
7+
Copyright 2025 Ankur Sinha
8+
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
9+
"""
10+
11+
import requests
12+
import datetime
13+
from dateutil.relativedelta import relativedelta
14+
import matplotlib.pyplot as plt
15+
import matplotlib
16+
17+
# repositories_url = "https://v2.opensourcebrain.org/proxy/workspaces/api/osbrepository?page=1&per_page=4000"
18+
repositories_url = "https://v2.opensourcebrain.org/proxy/workspaces/api/osbrepository?page=1&per_page=40"
19+
workspaces_url = (
20+
"https://v2.opensourcebrain.org/proxy/workspaces/api/workspace?page=1&per_page=1500"
21+
)
22+
23+
24+
def get_repo_metrics():
25+
"""Get metrics on number of repositories on OSBv2"""
26+
resp = requests.get(repositories_url)
27+
28+
resp.raise_for_status()
29+
data = resp.json()
30+
repositories = data["osbrepositories"]
31+
total_number = data["pagination"]["total"]
32+
assert total_number == len(repositories)
33+
print(f">>> Number of repositories: {len(repositories)}")
34+
35+
model_or_data = {"models": 0, "data": 0}
36+
repo_types = {"biomodels": 0, "dandi": 0, "github": 0, "figshare": 0}
37+
creation_time_stamps = []
38+
39+
for repo in repositories:
40+
repo_types[repo["repository_type"]] += 1
41+
if repo["content_types_list"][0] == "modeling":
42+
model_or_data["models"] += 1
43+
else:
44+
model_or_data["data"] += 1
45+
46+
creation_time_stamps.append(
47+
datetime.datetime.fromisoformat(repo["timestamp_created"])
48+
)
49+
50+
print(">>> Type break down:")
51+
print(model_or_data)
52+
print(repo_types)
53+
print(creation_time_stamps[0:10])
54+
print(creation_time_stamps[0:-10])
55+
return creation_time_stamps
56+
57+
58+
def get_workspace_metrics():
59+
"""Get metrics on number of repositories on OSBv2"""
60+
resp = requests.get(workspaces_url)
61+
62+
resp.raise_for_status()
63+
data = resp.json()
64+
workspaces = data["workspaces"]
65+
total_number = data["pagination"]["total"]
66+
assert total_number == len(workspaces)
67+
print(f">>> Number of workspaces: {len(workspaces)}")
68+
69+
public_or_private = {"public": 0, "private": 0}
70+
71+
creation_time_stamps = []
72+
73+
for ws in workspaces:
74+
if ws["publicable"]:
75+
public_or_private["public"] += 1
76+
else:
77+
public_or_private["private"] += 1
78+
creation_time_stamps.append(
79+
datetime.datetime.fromisoformat(ws["timestamp_created"])
80+
)
81+
82+
creation_time_stamps = sorted(creation_time_stamps)
83+
84+
print(">>> Type break down:")
85+
print(public_or_private)
86+
print(creation_time_stamps[0:10])
87+
print(creation_time_stamps[0:-10])
88+
return creation_time_stamps
89+
90+
91+
def plot_trend(title, creation_time_stamps, timewindow=2):
92+
time_now = datetime.datetime.now()
93+
trend_start = datetime.datetime(2021, 1, 1, 0, 0, 0)
94+
95+
plot_data_x = []
96+
plot_data_y = []
97+
98+
index = 0
99+
trend_point = trend_start
100+
for ts in creation_time_stamps:
101+
index += 1
102+
if ts.timestamp() > trend_point.timestamp():
103+
plot_data_x.append(trend_point)
104+
plot_data_y.append(index)
105+
trend_point += relativedelta(months=timewindow)
106+
107+
assert index == len(creation_time_stamps)
108+
plot_data_x.append(time_now)
109+
plot_data_y.append(index)
110+
111+
fig, ax = plt.subplots()
112+
ax.plot(
113+
plot_data_x,
114+
plot_data_y,
115+
)
116+
ax.set_xlabel("Date")
117+
ax.set_ylabel(f"Number of {title}")
118+
ax.spines["top"].set_visible(False)
119+
ax.spines["right"].set_visible(False)
120+
ax.xaxis.set(
121+
major_locator=matplotlib.dates.MonthLocator(interval=timewindow),
122+
major_formatter=matplotlib.dates.DateFormatter("%Y-%m"),
123+
)
124+
125+
plt.xticks(rotation=90)
126+
plt.tight_layout()
127+
plt.show()
128+
129+
130+
if __name__ == "__main__":
131+
repo_timestamps = get_repo_metrics()
132+
plot_trend(title="Repositories", creation_time_stamps=repo_timestamps)
133+
workspace_timestamps = get_workspace_metrics()
134+
plot_trend(title="Workspaces", creation_time_stamps=workspace_timestamps)

scripts/get-user-metrics.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to get user metrics and plot them. This can only be used by admins,
4+
since it requires access to the complete user list in KeyCloak.
5+
6+
File: get-user-metrics.py
7+
8+
Copyright 2025 Ankur Sinha
9+
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
10+
"""
11+
12+
import sys
13+
import datetime
14+
import requests
15+
import logging
16+
import matplotlib
17+
matplotlib.use('qtagg')
18+
import matplotlib.pyplot as plt
19+
import json
20+
from dateutil.relativedelta import relativedelta
21+
22+
logging.basicConfig(level=logging.DEBUG)
23+
logger = logging.getLogger(__name__)
24+
logger.setLevel(logging.DEBUG)
25+
26+
27+
def run(keycloak_user, keycloak_password, save_json=False):
28+
"""Main runner method.
29+
30+
:param keycloak_user: keycloak username
31+
:type keycloak_user: string
32+
:param keycloak_password: keycloak password
33+
:type keycloak_password: string
34+
:param save_json: whether the downloaded user data should be saved to file
35+
:type save_json: bool
36+
37+
"""
38+
keycloak_url = "https://accounts.v2.opensourcebrain.org/auth"
39+
keycloak_realm = "osb2"
40+
logger.debug(f"Username: {keycloak_user} Password: {keycloak_password}")
41+
42+
resp = requests.post(
43+
f"{keycloak_url}/realms/master/protocol/openid-connect/token",
44+
data={
45+
"client_id": "admin-cli",
46+
"username": keycloak_user,
47+
"password": keycloak_password,
48+
"grant_type": "password",
49+
},
50+
)
51+
resp.raise_for_status()
52+
data = resp.json()
53+
access_token = data["access_token"]
54+
logger.debug(access_token)
55+
56+
auth_headers = {"Authorization": f"Bearer {access_token}"}
57+
58+
resp = requests.get(
59+
f"{keycloak_url}/admin/realms/{keycloak_realm}/users/count",
60+
headers=auth_headers,
61+
)
62+
63+
resp.raise_for_status()
64+
data = resp.json()
65+
total_users = data
66+
print(f">>> Number of users: {total_users}")
67+
68+
resp = requests.get(
69+
f"{keycloak_url}/admin/realms/{keycloak_realm}/users",
70+
headers=auth_headers,
71+
params={"max": total_users},
72+
)
73+
74+
resp.raise_for_status()
75+
data = resp.json()
76+
77+
if save_json:
78+
with open("osbv2-user-data.json", 'w') as f:
79+
json.dump(data, f)
80+
81+
get_user_trends(data)
82+
83+
84+
def get_user_trends(data, timewindow=2):
85+
"""Get trends for user registration, and plot figure
86+
87+
:param data: user data from keycloak
88+
:type data: list[dict]
89+
:param timewindow: size of window/bin for plotting, in months
90+
:type timewindow: int
91+
"""
92+
time_now = datetime.datetime.now()
93+
trend_start = datetime.datetime(2021, 1, 1, 0, 0, 0)
94+
95+
# note, milliseconds
96+
creation_time_stamps = sorted([au["createdTimestamp"] / 1000 for au in data])
97+
98+
plot_data_x = []
99+
plot_data_y = []
100+
101+
index = 0
102+
trend_point = trend_start
103+
for ts in creation_time_stamps:
104+
index += 1
105+
if ts > trend_point.timestamp():
106+
plot_data_x.append(trend_point)
107+
plot_data_y.append(index)
108+
trend_point += relativedelta(months=timewindow)
109+
110+
assert index == len(creation_time_stamps)
111+
plot_data_x.append(time_now)
112+
plot_data_y.append(index)
113+
114+
fig, ax = plt.subplots()
115+
plt.title("OSBv2 users over time")
116+
ax.plot(
117+
plot_data_x,
118+
plot_data_y,
119+
)
120+
ax.set_xlabel("Date")
121+
ax.set_ylabel("Number of users")
122+
ax.spines["top"].set_visible(False)
123+
ax.spines["right"].set_visible(False)
124+
ax.xaxis.set(
125+
major_locator=matplotlib.dates.MonthLocator(interval=timewindow),
126+
major_formatter=matplotlib.dates.DateFormatter("%Y-%m"),
127+
)
128+
129+
plt.xticks(rotation=90)
130+
plt.tight_layout()
131+
plt.show()
132+
133+
134+
if __name__ == "__main__":
135+
if len(sys.argv) < 3:
136+
print("Error: two arguments required: username and password")
137+
sys.exit(-1)
138+
run(sys.argv[1], sys.argv[2])

scripts/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
matplotlib
2+
requests
3+
pyqt6
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
(contribute)=
2-
# Contribute
1+
(contribute:osb)=
2+
# Contribute to OSB development
33

44
There is a central repository for issues listing OSB projects looking for contributors. A number of other programming tasks (e.g. further development of NeuroML based tools) are also listed, which would be of benefit to OSB projects.
55

source/General/Contribute_To_OSB_docs.md renamed to source/Contribute/Contribute_To_OSB_docs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(general:contribute_docs)=
1+
(contribute:docs)=
22
# Contribute to OSB documentation
33

44
This documentation is written using [Jupyter Book](https://jupyterbook.org).

0 commit comments

Comments
 (0)