diff --git a/scripts/get-repo-workspace-metrics.py b/scripts/get-repo-workspace-metrics.py new file mode 100644 index 0000000..86c8179 --- /dev/null +++ b/scripts/get-repo-workspace-metrics.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +""" +Enter one line description here. + +File: + +Copyright 2025 Ankur Sinha +Author: Ankur Sinha +""" + +import requests +import datetime +from dateutil.relativedelta import relativedelta +import matplotlib.pyplot as plt +import matplotlib + +# repositories_url = "https://v2.opensourcebrain.org/proxy/workspaces/api/osbrepository?page=1&per_page=4000" +repositories_url = "https://v2.opensourcebrain.org/proxy/workspaces/api/osbrepository?page=1&per_page=40" +workspaces_url = ( + "https://v2.opensourcebrain.org/proxy/workspaces/api/workspace?page=1&per_page=1500" +) + + +def get_repo_metrics(): + """Get metrics on number of repositories on OSBv2""" + resp = requests.get(repositories_url) + + resp.raise_for_status() + data = resp.json() + repositories = data["osbrepositories"] + total_number = data["pagination"]["total"] + assert total_number == len(repositories) + print(f">>> Number of repositories: {len(repositories)}") + + model_or_data = {"models": 0, "data": 0} + repo_types = {"biomodels": 0, "dandi": 0, "github": 0, "figshare": 0} + creation_time_stamps = [] + + for repo in repositories: + repo_types[repo["repository_type"]] += 1 + if repo["content_types_list"][0] == "modeling": + model_or_data["models"] += 1 + else: + model_or_data["data"] += 1 + + creation_time_stamps.append( + datetime.datetime.fromisoformat(repo["timestamp_created"]) + ) + + print(">>> Type break down:") + print(model_or_data) + print(repo_types) + print(creation_time_stamps[0:10]) + print(creation_time_stamps[0:-10]) + return creation_time_stamps + + +def get_workspace_metrics(): + """Get metrics on number of repositories on OSBv2""" + resp = requests.get(workspaces_url) + + resp.raise_for_status() + data = resp.json() + workspaces = data["workspaces"] + total_number = data["pagination"]["total"] + assert total_number == len(workspaces) + print(f">>> Number of workspaces: {len(workspaces)}") + + public_or_private = {"public": 0, "private": 0} + + creation_time_stamps = [] + + for ws in workspaces: + if ws["publicable"]: + public_or_private["public"] += 1 + else: + public_or_private["private"] += 1 + creation_time_stamps.append( + datetime.datetime.fromisoformat(ws["timestamp_created"]) + ) + + creation_time_stamps = sorted(creation_time_stamps) + + print(">>> Type break down:") + print(public_or_private) + print(creation_time_stamps[0:10]) + print(creation_time_stamps[0:-10]) + return creation_time_stamps + + +def plot_trend(title, creation_time_stamps, timewindow=2): + time_now = datetime.datetime.now() + trend_start = datetime.datetime(2021, 1, 1, 0, 0, 0) + + plot_data_x = [] + plot_data_y = [] + + index = 0 + trend_point = trend_start + for ts in creation_time_stamps: + index += 1 + if ts.timestamp() > trend_point.timestamp(): + plot_data_x.append(trend_point) + plot_data_y.append(index) + trend_point += relativedelta(months=timewindow) + + assert index == len(creation_time_stamps) + plot_data_x.append(time_now) + plot_data_y.append(index) + + fig, ax = plt.subplots() + ax.plot( + plot_data_x, + plot_data_y, + ) + ax.set_xlabel("Date") + ax.set_ylabel(f"Number of {title}") + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.xaxis.set( + major_locator=matplotlib.dates.MonthLocator(interval=timewindow), + major_formatter=matplotlib.dates.DateFormatter("%Y-%m"), + ) + + plt.xticks(rotation=90) + plt.tight_layout() + plt.show() + + +if __name__ == "__main__": + repo_timestamps = get_repo_metrics() + plot_trend(title="Repositories", creation_time_stamps=repo_timestamps) + workspace_timestamps = get_workspace_metrics() + plot_trend(title="Workspaces", creation_time_stamps=workspace_timestamps) diff --git a/scripts/get-user-metrics.py b/scripts/get-user-metrics.py new file mode 100644 index 0000000..842b510 --- /dev/null +++ b/scripts/get-user-metrics.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +""" +Script to get user metrics and plot them. This can only be used by admins, +since it requires access to the complete user list in KeyCloak. + +File: get-user-metrics.py + +Copyright 2025 Ankur Sinha +Author: Ankur Sinha +""" + +import sys +import datetime +import requests +import logging +import matplotlib +matplotlib.use('qtagg') +import matplotlib.pyplot as plt +import json +from dateutil.relativedelta import relativedelta + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +def run(keycloak_user, keycloak_password, save_json=False): + """Main runner method. + + :param keycloak_user: keycloak username + :type keycloak_user: string + :param keycloak_password: keycloak password + :type keycloak_password: string + :param save_json: whether the downloaded user data should be saved to file + :type save_json: bool + + """ + keycloak_url = "https://accounts.v2.opensourcebrain.org/auth" + keycloak_realm = "osb2" + logger.debug(f"Username: {keycloak_user} Password: {keycloak_password}") + + resp = requests.post( + f"{keycloak_url}/realms/master/protocol/openid-connect/token", + data={ + "client_id": "admin-cli", + "username": keycloak_user, + "password": keycloak_password, + "grant_type": "password", + }, + ) + resp.raise_for_status() + data = resp.json() + access_token = data["access_token"] + logger.debug(access_token) + + auth_headers = {"Authorization": f"Bearer {access_token}"} + + resp = requests.get( + f"{keycloak_url}/admin/realms/{keycloak_realm}/users/count", + headers=auth_headers, + ) + + resp.raise_for_status() + data = resp.json() + total_users = data + print(f">>> Number of users: {total_users}") + + resp = requests.get( + f"{keycloak_url}/admin/realms/{keycloak_realm}/users", + headers=auth_headers, + params={"max": total_users}, + ) + + resp.raise_for_status() + data = resp.json() + + if save_json: + with open("osbv2-user-data.json", 'w') as f: + json.dump(data, f) + + get_user_trends(data) + + +def get_user_trends(data, timewindow=2): + """Get trends for user registration, and plot figure + + :param data: user data from keycloak + :type data: list[dict] + :param timewindow: size of window/bin for plotting, in months + :type timewindow: int + """ + time_now = datetime.datetime.now() + trend_start = datetime.datetime(2021, 1, 1, 0, 0, 0) + + # note, milliseconds + creation_time_stamps = sorted([au["createdTimestamp"] / 1000 for au in data]) + + plot_data_x = [] + plot_data_y = [] + + index = 0 + trend_point = trend_start + for ts in creation_time_stamps: + index += 1 + if ts > trend_point.timestamp(): + plot_data_x.append(trend_point) + plot_data_y.append(index) + trend_point += relativedelta(months=timewindow) + + assert index == len(creation_time_stamps) + plot_data_x.append(time_now) + plot_data_y.append(index) + + fig, ax = plt.subplots() + plt.title("OSBv2 users over time") + ax.plot( + plot_data_x, + plot_data_y, + ) + ax.set_xlabel("Date") + ax.set_ylabel("Number of users") + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.xaxis.set( + major_locator=matplotlib.dates.MonthLocator(interval=timewindow), + major_formatter=matplotlib.dates.DateFormatter("%Y-%m"), + ) + + plt.xticks(rotation=90) + plt.tight_layout() + plt.show() + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Error: two arguments required: username and password") + sys.exit(-1) + run(sys.argv[1], sys.argv[2]) diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..3a8f40a --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,3 @@ +matplotlib +requests +pyqt6 diff --git a/source/General/Contribute_To_OSB.md b/source/Contribute/Contribute_To_OSB.md similarity index 94% rename from source/General/Contribute_To_OSB.md rename to source/Contribute/Contribute_To_OSB.md index 89db38c..a1ba216 100644 --- a/source/General/Contribute_To_OSB.md +++ b/source/Contribute/Contribute_To_OSB.md @@ -1,5 +1,5 @@ -(contribute)= -# Contribute +(contribute:osb)= +# Contribute to OSB development 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. diff --git a/source/General/Contribute_To_OSB_docs.md b/source/Contribute/Contribute_To_OSB_docs.md similarity index 96% rename from source/General/Contribute_To_OSB_docs.md rename to source/Contribute/Contribute_To_OSB_docs.md index f950178..50c7c1b 100644 --- a/source/General/Contribute_To_OSB_docs.md +++ b/source/Contribute/Contribute_To_OSB_docs.md @@ -1,4 +1,4 @@ -(general:contribute_docs)= +(contribute:docs)= # Contribute to OSB documentation This documentation is written using [Jupyter Book](https://jupyterbook.org). diff --git a/source/Contribute/Test_OSB.md b/source/Contribute/Test_OSB.md new file mode 100644 index 0000000..bb2b920 --- /dev/null +++ b/source/Contribute/Test_OSB.md @@ -0,0 +1,209 @@ +(contribute:test)= +# Test OSB + +Testing OSB and reporting any issues that you find is another great way of contributing to the development of OSB. +Here is a user testing template that can be used in a GitHub issue. +Each test can be marked `PASS` or `FAIL` to indicate whether an issue was found. +We have marked all tests as passing here as an example. +Please add any tests that we have missed. +The more complete the test list/matrix, the more robust and reliable OSB will be for its users. + +The development deployment is tested before being deployed on the production infrastructure. +The URL for the development deployment is: https://v2dev.opensourcebrain.org + + +```{code-block} markdown + +# Testing template for OSBv2. + +- Deployment: development/production +- Date/time: + +Statuses: + +- PASS: test passes +- FAIL: test fails +- NA: not applicable + + +## Website loading + +- Website loads: PASS + +### Services + +Note that while everyone can check if these services are up, only admins will be able to login to check logs and so on. + +- KeyCloak is up: https://accounts.v2dev.opensourcebrain.org/: PASS +- Argo is up: https://argo.v2dev.opensourcebrain.org/: PASS +- Swagger is up: ? +- Sentry is up: ? + + +## Left side bar + +- All links are valid and work: PASS +- Workspaces button works: PASS +- Repositories button works: PASS +- "Create new" button shows "login" dialog: PASS + +## Usage before signing in + +- Featured workspaces load: PASS + +### Workspaces + +- Public workspaces load: PASS +- No private workspaces load: PASS +- Workspaces: icon view works: PASS +- Workspaces: list view works: PASS +- Text search functionality works in both views: PASS +- Text search is maintained when switching between public and featured workspaces: PASS +- Tag search functionality works in both views: PASS +- Tag search is marked when active in both views: PASS +- Deslecting tag removes it from search: PASS +- Selecting a tag activates search: PASS +- Pagination works: PASS +- Clicking on a workspace title opens workspace: PASS + +### Repositories + +- Repository index loads: PASS +- Text search functionality works in both views: PASS +- Tag search functionality works in both views: PASS +- Tag search is marked when active in both views: PASS +- Deslecting tag removes it from search: PASS +- Pagination works: PASS +- Repository entry username is correct: FAIL (capitalised?) + +## Authentication + +- Login button goes to the login page: PASS + +### New user registration + +- New user registration link works: PASS +- Form validation for e-mail works: PASS +- Form validation for username works: FAIL +- Register with Orcid goes to ORCID authentication: PASS +- Register with GitHub goes to GitHub authentication: PASS + +### Login + +- Logging in with valid username/password works: PASS +- Logging in with invalid username/password works: PASS + +## Logged in user + +- New user greeted with "Create your first workspace": PASS + +### My account + +- Link works and page loads: PASS +- "Manage running workspaces" link works: PASS +- "Edit my profile" button works: PASS + +#### Edit my profile + +- Profile picture URL addition works: PASS +- Changing display name works: FAIL +- Changing e-mail works: PASS +- Adding links works and profile page is updated: PASS + +## Adding new repository + +### General + +- Invalid repository URL shows error: PASS +- Image upload works: PASS +- Valid repository URL loads metadata: + + - GitHub: PASS + - DANDI: FAIL: intermittently fails when URL is pasted + - FigShare: FAIL: unclear what valid FigShare URLs are. Perhaps the error message should specify to help users. + - BioModels: PASS + +- Repository is correctly added: PASS + + - GitHub: PASS + - DANDI: FAIL: intermittently fails when URL is pasted + - FigShare: NA + - BioModels: PASS + +- Repository listed in user page under repositories tab: PASS + + - GitHub: PASS + - DANDI: FAIL: intermittently fails when URL is pasted + - FigShare: NA + - BioModels: + +- Repository duplication is avoided: PASS: looks like one can add duplicates + + +## Repository detail page + + +- File list loads correctly: + + - GitHub: PASS + - DANDI: PASS + - FigShare: NA + - BioModels: PASS + + +## Workspace creation + +### From repository + +- New workspace from selection works: without selections: + + - GitHub: PASS + - DANDI: UNTESTED (large data files) + - FigShare: NA + - BioModels: PASS + +- New workspace from selection works: with selections: + + - GitHub: PASS + - DANDI: UNTESTED (large data files) + - FigShare: NA + - BioModels: PASS + +- Workstation files are downloaded correctly + + - GitHub: PASS + - DANDI: UNTESTED (large data files) + - FigShare: NA + - BioModels: FAIL (needs more testing: file list was present, but not the file) + + +- New workspace from selection: workspaces are private by default: PASS +- Private workspaces are clearly marked: FAIL (no indicator that a workspace is private?) + +### Not from repository + +- New workspace can be created for JupyterLab: PASS +- New workspace can be created for NetPyNE-UI: PASS +- New workspace can be created for NWBE: PASS + +## Applications + +- Left side bar works: PASS +- Resources appear in left hand side bar: PASS + +### JupyterLab + +- JupyterLab loads correctly: PASS + +### NWB Explorer + +- NWBE loads correctly: PASS +- Clicking on acquisition/stimulus opens plots: FAIL (needs to be checked on prod) + +### NetPyNE-UI + +- NetPyNE-UI loads correctly: PASS +- Examples are loaded correctly: PASS +- NetPyNE-UI works correctly: PASS (tested with first example) + +``` diff --git a/source/General/Metrics.md b/source/General/Metrics.md new file mode 100644 index 0000000..1365849 --- /dev/null +++ b/source/General/Metrics.md @@ -0,0 +1,15 @@ +(metrics)= +# Usage metrics + +The plot shows the trend in number of OSBv2 users over time. +It is periodically updated using the script [here](https://github.com/OpenSourceBrain/Documentation/blob/main/scripts/get-user-metrics.py). +Please note that this script can only be run by OSBv2 administrators as it requires access to the user database that non-admin users will not have. + +For usage metrics for OSBv1, please refer to [this page](https://v1.opensourcebrain.org/projects#people). + +```{figure} ../images/20250828-osb-users-plot.png +:alt: Plot showing number of OSBv2 users over time +:align: center +:width: 600px + +``` diff --git a/source/_toc.yml b/source/_toc.yml index cf9d4d7..2673244 100644 --- a/source/_toc.yml +++ b/source/_toc.yml @@ -23,7 +23,6 @@ parts: - file: OSBv1/Running_Simulations_on_OSB - file: OSBv1/Creating_Tutorials - file: OSBv1/Troubleshooting_3d_explorer - - caption: OSB version 2 chapters: - file: OSBv2/Overview @@ -44,9 +43,8 @@ parts: - caption: Community chapters: - file: General/CoC + - file: General/Metrics - file: General/Contacts - - file: General/Contribute_To_OSB - - file: General/Contribute_To_OSB_docs - file: General/Meetings sections: - file: General/Meetings/CNS_2022 @@ -62,6 +60,11 @@ parts: - file: General/Meetings/Sardinia_2013 - file: General/PrivacyPolicy - file: Reference/zBibliography + - caption: Contribute + chapters: + - file: Contribute/Contribute_To_OSB_docs + - file: Contribute/Contribute_To_OSB + - file: Contribute/Test_OSB - caption: Useful Information chapters: - file: General/Background_Information/01_Introduction_to_Source_Control diff --git a/source/images/20250828-osb-users-plot.png b/source/images/20250828-osb-users-plot.png new file mode 100644 index 0000000..e36cbc6 Binary files /dev/null and b/source/images/20250828-osb-users-plot.png differ diff --git a/source/images/osbv2-main-page-repositories-link-with-text.png b/source/images/osbv2-main-page-repositories-link-with-text.png index 4b9517b..c76fb89 100644 Binary files a/source/images/osbv2-main-page-repositories-link-with-text.png and b/source/images/osbv2-main-page-repositories-link-with-text.png differ diff --git a/source/images/osbv2-main-page-repositories-link-with-text.svg b/source/images/osbv2-main-page-repositories-link-with-text.svg index 7376922..f97c77a 100644 --- a/source/images/osbv2-main-page-repositories-link-with-text.svg +++ b/source/images/osbv2-main-page-repositories-link-with-text.svg @@ -12,14 +12,13 @@ inkscape:export-filename="osbv2-main-page-repositories-link-with-text.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" + xml:space="preserve" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"> - - - - - - - - - - - Use the Use the dashboard to dashboard to access the access the repository list repository list page or create page or create a new a new repository - repository - - - - + sodipodi:nodetypes="cc" /> diff --git a/source/images/osbv2-main-page-repositories-link.png b/source/images/osbv2-main-page-repositories-link.png index 74c4b52..1fcc79e 100644 Binary files a/source/images/osbv2-main-page-repositories-link.png and b/source/images/osbv2-main-page-repositories-link.png differ diff --git a/source/images/osbv2-workspaces-homepage-create-with-text.png b/source/images/osbv2-workspaces-homepage-create-with-text.png index ff452ce..0efefce 100644 Binary files a/source/images/osbv2-workspaces-homepage-create-with-text.png and b/source/images/osbv2-workspaces-homepage-create-with-text.png differ diff --git a/source/images/osbv2-workspaces-homepage-create-with-text.svg b/source/images/osbv2-workspaces-homepage-create-with-text.svg index 614ca35..e8f8fde 100644 --- a/source/images/osbv2-workspaces-homepage-create-with-text.svg +++ b/source/images/osbv2-workspaces-homepage-create-with-text.svg @@ -12,14 +12,13 @@ inkscape:export-filename="osbv2-workspaces-homepage-create-with-text.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" + xml:space="preserve" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"> - - - - - - - - - - Create a new workspace Create a new workspace using the provided using the provided options - options - - - + sodipodi:nodetypes="cc" /> diff --git a/source/images/osbv2-workspaces-homepage-create.png b/source/images/osbv2-workspaces-homepage-create.png index 58cf135..d05a0a3 100644 Binary files a/source/images/osbv2-workspaces-homepage-create.png and b/source/images/osbv2-workspaces-homepage-create.png differ diff --git a/source/images/osbv2-workspaces-homepage-with-text.png b/source/images/osbv2-workspaces-homepage-with-text.png index 0fc4dc8..cf75289 100644 Binary files a/source/images/osbv2-workspaces-homepage-with-text.png and b/source/images/osbv2-workspaces-homepage-with-text.png differ diff --git a/source/images/osbv2-workspaces-homepage-with-text.svg b/source/images/osbv2-workspaces-homepage-with-text.svg index 4e8697a..c3a1d66 100644 --- a/source/images/osbv2-workspaces-homepage-with-text.svg +++ b/source/images/osbv2-workspaces-homepage-with-text.svg @@ -12,14 +12,13 @@ inkscape:export-filename="osbv2-workspaces-homepage-with-text.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" + xml:space="preserve" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"> - - - - - - - - - - Click on a workspace to Click on a workspace to open it or use the menu open it or use the menu to open it with a specific to open it with a specific OSBv2 application and OSBv2 application and carry out other actions - carry out other actions - - - Switch between your Switch between your workspaces, features workspaces, featured workspaces, and other workspaces, and other publicly shared publicly shared workspaces - workspaces - - Filter workspaces by Filter workspaces by tags or use a free text tags or use a free text search - search - - + sodipodi:nodetypes="cc" /> diff --git a/source/images/osbv2-workspaces-homepage.png b/source/images/osbv2-workspaces-homepage.png index 1edcb26..fa800a7 100644 Binary files a/source/images/osbv2-workspaces-homepage.png and b/source/images/osbv2-workspaces-homepage.png differ