notebook-metrics 1.0.0a1__tar.gz

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.
Files changed (57) hide show
  1. notebook_metrics-1.0.0a1/.copier-answers.yml +14 -0
  2. notebook_metrics-1.0.0a1/.gitignore +129 -0
  3. notebook_metrics-1.0.0a1/.prettierignore +6 -0
  4. notebook_metrics-1.0.0a1/.yarnrc.yml +1 -0
  5. notebook_metrics-1.0.0a1/CHANGELOG.md +5 -0
  6. notebook_metrics-1.0.0a1/LICENSE +26 -0
  7. notebook_metrics-1.0.0a1/ONBOARDING.md +274 -0
  8. notebook_metrics-1.0.0a1/PKG-INFO +250 -0
  9. notebook_metrics-1.0.0a1/README.md +190 -0
  10. notebook_metrics-1.0.0a1/RELEASE.md +148 -0
  11. notebook_metrics-1.0.0a1/babel.config.js +1 -0
  12. notebook_metrics-1.0.0a1/conftest.py +8 -0
  13. notebook_metrics-1.0.0a1/install.json +5 -0
  14. notebook_metrics-1.0.0a1/jest.config.js +31 -0
  15. notebook_metrics-1.0.0a1/jupyter-config/server-config/notebook_metrics.json +7 -0
  16. notebook_metrics-1.0.0a1/notebook_metrics/__init__.py +44 -0
  17. notebook_metrics-1.0.0a1/notebook_metrics/_version.py +4 -0
  18. notebook_metrics-1.0.0a1/notebook_metrics/emissions/command-executed.yml +42 -0
  19. notebook_metrics-1.0.0a1/notebook_metrics/emissions/current-changed.yml +31 -0
  20. notebook_metrics-1.0.0a1/notebook_metrics/emissions/jupyter-error.yml +45 -0
  21. notebook_metrics-1.0.0a1/notebook_metrics/emissions/runtime-error.yml +33 -0
  22. notebook_metrics-1.0.0a1/notebook_metrics/labextension/package.json +212 -0
  23. notebook_metrics-1.0.0a1/notebook_metrics/labextension/schemas/notebook-metrics/dispatcher.json +55 -0
  24. notebook_metrics-1.0.0a1/notebook_metrics/labextension/schemas/notebook-metrics/package.json.orig +207 -0
  25. notebook_metrics-1.0.0a1/notebook_metrics/labextension/static/547.d2fe90605306675c07f8.js +1 -0
  26. notebook_metrics-1.0.0a1/notebook_metrics/labextension/static/728.3fabb3bacc277b6a9f4b.js +1 -0
  27. notebook_metrics-1.0.0a1/notebook_metrics/labextension/static/remoteEntry.c547d360419976803e19.js +1 -0
  28. notebook_metrics-1.0.0a1/notebook_metrics/labextension/static/style.js +4 -0
  29. notebook_metrics-1.0.0a1/notebook_metrics/labextension/static/third-party-licenses.json +16 -0
  30. notebook_metrics-1.0.0a1/notebook_metrics/tests/__init__.py +1 -0
  31. notebook_metrics-1.0.0a1/notebook_metrics/tests/test_pass.py +50 -0
  32. notebook_metrics-1.0.0a1/package.json +207 -0
  33. notebook_metrics-1.0.0a1/pyproject.toml +89 -0
  34. notebook_metrics-1.0.0a1/schema/dispatcher.json +55 -0
  35. notebook_metrics-1.0.0a1/setup.py +1 -0
  36. notebook_metrics-1.0.0a1/src/__tests__/notebook_metrics.spec.ts +334 -0
  37. notebook_metrics-1.0.0a1/src/emissions/command-executed.ts +62 -0
  38. notebook_metrics-1.0.0a1/src/emissions/current-changed.ts +54 -0
  39. notebook_metrics-1.0.0a1/src/emissions/jupyter-error.ts +82 -0
  40. notebook_metrics-1.0.0a1/src/emissions/runtime-error.ts +55 -0
  41. notebook_metrics-1.0.0a1/src/index.ts +3 -0
  42. notebook_metrics-1.0.0a1/src/metrics.ts +113 -0
  43. notebook_metrics-1.0.0a1/src/plugins.ts +165 -0
  44. notebook_metrics-1.0.0a1/style/base.css +5 -0
  45. notebook_metrics-1.0.0a1/style/index.css +1 -0
  46. notebook_metrics-1.0.0a1/style/index.js +1 -0
  47. notebook_metrics-1.0.0a1/tsconfig.json +26 -0
  48. notebook_metrics-1.0.0a1/tsconfig.test.json +3 -0
  49. notebook_metrics-1.0.0a1/ui-tests/README.md +171 -0
  50. notebook_metrics-1.0.0a1/ui-tests/event_capture_extension.py +45 -0
  51. notebook_metrics-1.0.0a1/ui-tests/jupyter_server_test_config.py +38 -0
  52. notebook_metrics-1.0.0a1/ui-tests/metrics-override.yml +8 -0
  53. notebook_metrics-1.0.0a1/ui-tests/package.json +18 -0
  54. notebook_metrics-1.0.0a1/ui-tests/playwright.config.js +20 -0
  55. notebook_metrics-1.0.0a1/ui-tests/tests/notebook_metrics.spec.ts +124 -0
  56. notebook_metrics-1.0.0a1/ui-tests/yarn.lock +5967 -0
  57. notebook_metrics-1.0.0a1/yarn.lock +11196 -0
@@ -0,0 +1,14 @@
1
+ # Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
2
+ _commit: v4.3.8
3
+ _src_path: https://github.com/jupyterlab/extension-template
4
+ author_email: git@darian.link
5
+ author_name: Afshin T. Darian
6
+ has_binder: false
7
+ has_settings: true
8
+ kind: server
9
+ labextension_name: notebook-metrics
10
+ project_short_description: A JupyterLab/JupyterLite extension for Jupyter UI metrics.
11
+ python_name: notebook_metrics
12
+ repository: https://github.com/notebook-link/metrics
13
+ test: true
14
+
@@ -0,0 +1,129 @@
1
+ *.bundle.*
2
+ lib/
3
+ node_modules/
4
+ *.log
5
+ .eslintcache
6
+ .stylelintcache
7
+ *.egg-info/
8
+ .ipynb_checkpoints
9
+ *.tsbuildinfo
10
+ notebook_metrics/labextension
11
+ # Version file is handled by hatchling
12
+ notebook_metrics/_version.py
13
+
14
+ # Integration tests
15
+ ui-tests/test-results/
16
+ ui-tests/playwright-report/
17
+
18
+ # Created by https://www.gitignore.io/api/python
19
+ # Edit at https://www.gitignore.io/?templates=python
20
+
21
+ ### Python ###
22
+ # Byte-compiled / optimized / DLL files
23
+ __pycache__/
24
+ *.py[cod]
25
+ *$py.class
26
+
27
+ # C extensions
28
+ *.so
29
+
30
+ # Distribution / packaging
31
+ .Python
32
+ build/
33
+ develop-eggs/
34
+ dist/
35
+ downloads/
36
+ eggs/
37
+ .eggs/
38
+ lib/
39
+ lib64/
40
+ parts/
41
+ sdist/
42
+ var/
43
+ wheels/
44
+ pip-wheel-metadata/
45
+ share/python-wheels/
46
+ .installed.cfg
47
+ *.egg
48
+ MANIFEST
49
+
50
+ # PyInstaller
51
+ # Usually these files are written by a python script from a template
52
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
53
+ *.manifest
54
+ *.spec
55
+
56
+ # Installer logs
57
+ pip-log.txt
58
+ pip-delete-this-directory.txt
59
+
60
+ # Unit test / coverage reports
61
+ junit.xml
62
+ htmlcov/
63
+ .tox/
64
+ .nox/
65
+ .coverage
66
+ .coverage.*
67
+ .cache
68
+ nosetests.xml
69
+ coverage/
70
+ coverage.xml
71
+ *.cover
72
+ .hypothesis/
73
+ .pytest_cache/
74
+
75
+ # Translations
76
+ *.mo
77
+ *.pot
78
+
79
+ # Scrapy stuff:
80
+ .scrapy
81
+
82
+ # Sphinx documentation
83
+ docs/_build/
84
+
85
+ # PyBuilder
86
+ target/
87
+
88
+ # pyenv
89
+ .python-version
90
+
91
+ # celery beat schedule file
92
+ celerybeat-schedule
93
+
94
+ # SageMath parsed files
95
+ *.sage.py
96
+
97
+ # Spyder project settings
98
+ .spyderproject
99
+ .spyproject
100
+
101
+ # Rope project settings
102
+ .ropeproject
103
+
104
+ # Mr Developer
105
+ .mr.developer.cfg
106
+ .project
107
+ .pydevproject
108
+
109
+ # mkdocs documentation
110
+ /site
111
+
112
+ # mypy
113
+ .mypy_cache/
114
+ .dmypy.json
115
+ dmypy.json
116
+
117
+ # Pyre type checker
118
+ .pyre/
119
+
120
+ # End of https://www.gitignore.io/api/python
121
+
122
+ # OSX files
123
+ .DS_Store
124
+
125
+ # Yarn cache
126
+ .yarn/
127
+
128
+ # VS Code
129
+ .vscode
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ **/node_modules
3
+ **/lib
4
+ **/package.json
5
+ !/package.json
6
+ notebook_metrics
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ <!-- <START NEW CHANGELOG ENTRY> -->
4
+
5
+ <!-- <END NEW CHANGELOG ENTRY> -->
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2026, Afshin T. Darian
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ 3. Neither the name of the copyright holder nor the names of its contributors
14
+ may be used to endorse or promote products derived from this software
15
+ without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,274 @@
1
+ # Onboarding Guide
2
+
3
+ This package is infrastructure for metrics collection in JupyterLab and
4
+ JupyterLite. It is not a complete analytics product by itself.
5
+
6
+ If you are new to the project, the most important thing to know is this:
7
+
8
+ 1. Installing the package is not enough to observe data.
9
+ 2. The default frontend collector is a no-op.
10
+ 3. The dispatcher settings default to a state that blocks the built-in events.
11
+
12
+ To see useful metrics, you need two things at the same time:
13
+
14
+ 1. Emissions enabled.
15
+ 2. A consumer for those emissions.
16
+
17
+ For JupyterLab, the consumer can live in the frontend as a custom collector
18
+ plugin, or on the backend as a Jupyter Server event listener. For JupyterLite,
19
+ only the frontend path applies.
20
+
21
+ ## Mental Model
22
+
23
+ The package has four moving parts.
24
+
25
+ 1. The Python server extension `notebook_metrics` registers the event
26
+ schemas with Jupyter Server. It can also inject a `PageConfig` override from
27
+ a YAML file pointed to by `NOTEBOOK_METRICS_OVERRIDE`.
28
+ 2. The frontend broadcasts plugin wires JupyterLab signals and browser events
29
+ into metrics emissions.
30
+ 3. The frontend dispatcher plugin listens to the Jupyter events stream,
31
+ applies filtering, and forwards accepted events to a collector.
32
+ 4. The default frontend collector plugin intentionally does nothing. It exists
33
+ so the package can load cleanly before you replace it.
34
+
35
+ That means there are two valid integration styles:
36
+
37
+ 1. Frontend collection: provide your own `IMetrics.ICollector` plugin.
38
+ 2. Backend collection: listen to the registered schema events on
39
+ `app.event_logger`.
40
+
41
+ The working examples in this repository follow both patterns:
42
+
43
+ 1. Frontend filtering and dispatch are covered in
44
+ [src/**tests**/notebook_metrics.spec.ts](./src/__tests__/notebook_metrics.spec.ts).
45
+ 2. Backend listener collection is demonstrated in
46
+ [notebook_metrics/tests/test_pass.py](./notebook_metrics/tests/test_pass.py)
47
+ and [ui-tests/event_capture_extension.py](./ui-tests/event_capture_extension.py).
48
+
49
+ ## What Ships In The Box
50
+
51
+ The package emits four schema'd events.
52
+
53
+ 1. `https://schema.notebook.link/metrics/command-executed/v1`
54
+ Fired when the JupyterLab command registry executes a command.
55
+ 2. `https://schema.notebook.link/metrics/current-changed/v1`
56
+ Fired when the shell emits a non-null `currentChanged` value.
57
+ 3. `https://schema.notebook.link/metrics/jupyter-error/v1`
58
+ Fired when notebook cell execution fails with a Jupyter error.
59
+ 4. `https://schema.notebook.link/metrics/runtime-error/v1`
60
+ Fired for window-level `error` and `unhandledpromiserejection` events.
61
+
62
+ Today, all four built-in events are marked the same way:
63
+
64
+ 1. `anonymous: false`
65
+ 2. `sensitivity: high`
66
+
67
+ That has an important consequence for onboarding: if you leave the defaults in
68
+ place, none of the built-in events will pass the filter.
69
+
70
+ ## First Successful Setup
71
+
72
+ This is the shortest path to a real signal in JupyterLab.
73
+
74
+ ### 1. Install The Package
75
+
76
+ Install it with:
77
+
78
+ ```bash
79
+ python -m pip install notebook-metrics
80
+ ```
81
+
82
+ Use `notebook-metrics` for installation, but `notebook_metrics` for Python
83
+ imports and the server extension name.
84
+
85
+ ### 2. Enable Emissions
86
+
87
+ You can enable emissions either through JupyterLab user settings or through a
88
+ server-side page-config override.
89
+
90
+ For first-time evaluation, the override file is the clearest option because it
91
+ is explicit and wins over user settings.
92
+
93
+ Create a YAML file like this:
94
+
95
+ ```yaml
96
+ anonymous: false
97
+ disabled: false
98
+ excluded:
99
+ 'https://schema.notebook.link/metrics/command-executed/v1': false
100
+ 'https://schema.notebook.link/metrics/current-changed/v1': false
101
+ 'https://schema.notebook.link/metrics/jupyter-error/v1': false
102
+ 'https://schema.notebook.link/metrics/runtime-error/v1': false
103
+ sensitivity: high
104
+ ```
105
+
106
+ Then point `NOTEBOOK_METRICS_OVERRIDE` at it before starting JupyterLab.
107
+
108
+ Why this exact file matters:
109
+
110
+ 1. `disabled: false` turns the dispatcher on.
111
+ 2. `anonymous: false` allows the shipped non-anonymous events through.
112
+ 3. `sensitivity: high` allows the shipped high-sensitivity events through.
113
+ 4. `excluded` keeps each built-in schema enabled.
114
+
115
+ ### 3. Choose A Consumer Path
116
+
117
+ At this point the package is allowed to emit, but you still need something to
118
+ observe or store the events.
119
+
120
+ Choose one of these paths:
121
+
122
+ 1. If you want browser-side handling, add a custom frontend collector plugin.
123
+ 2. If you want server-side handling, add a Jupyter Server listener.
124
+
125
+ ## Path A: Frontend Collector
126
+
127
+ Use this when you want to forward metrics from the JupyterLab frontend to your
128
+ own service, local storage, or application-specific telemetry pipeline.
129
+
130
+ The collector contract is intentionally small:
131
+
132
+ ```ts
133
+ interface ICollector {
134
+ collect: (schema: string, event: IMetrics.Event<unknown>) => Promise<void>;
135
+ }
136
+ ```
137
+
138
+ Your custom plugin needs to provide `IMetrics.ICollector`.
139
+
140
+ ```ts
141
+ import type { JupyterFrontEndPlugin } from '@jupyterlab/application';
142
+ import { IMetrics } from 'notebook-metrics';
143
+
144
+ const collector: JupyterFrontEndPlugin<IMetrics.ICollector> = {
145
+ id: 'acme-metrics:collector',
146
+ autoStart: true,
147
+ provides: IMetrics.ICollector,
148
+ activate: () => ({
149
+ collect: async (schema, event) => {
150
+ console.log('metric', schema, event.metrics);
151
+ // Replace this with your real sink.
152
+ }
153
+ })
154
+ };
155
+
156
+ export default collector;
157
+ ```
158
+
159
+ Production note: disable the built-in no-op collector plugin
160
+ `notebook-metrics:collector` when your application ships a real one.
161
+
162
+ Good first smoke test:
163
+
164
+ 1. Start JupyterLab with the permissive override shown above.
165
+ 2. Load your collector plugin.
166
+ 3. Execute a known command such as changing the JupyterLab theme.
167
+ 4. Confirm your collector receives
168
+ `https://schema.notebook.link/metrics/command-executed/v1`.
169
+
170
+ ## Path B: Backend Listener
171
+
172
+ Use this when you want to keep collection on the Jupyter Server side, or when
173
+ you want the server to fan out metrics to another system.
174
+
175
+ The server extension already registers the schemas. Your job is to attach a
176
+ listener to the server event logger.
177
+
178
+ This minimal server extension mirrors the test-only capture extension in this
179
+ repository.
180
+
181
+ ```python
182
+ from notebook_metrics import schemas
183
+
184
+ PREFIX = 'https://schema.notebook.link/metrics'
185
+
186
+
187
+ def _jupyter_server_extension_points():
188
+ return [{'module': 'acme_metrics_listener'}]
189
+
190
+
191
+ def _load_jupyter_server_extension(app):
192
+ async def listener(logger, schema_id, data):
193
+ app.log.info('metric %s %s', schema_id, data.get('metrics', {}))
194
+
195
+ for schema in schemas:
196
+ app.event_logger.add_listener(
197
+ listener=listener,
198
+ schema_id=f'{PREFIX}/{schema}/v1',
199
+ )
200
+ ```
201
+
202
+ You can see the same pattern in
203
+ [ui-tests/event_capture_extension.py](./ui-tests/event_capture_extension.py).
204
+
205
+ Good first smoke test:
206
+
207
+ 1. Enable `notebook_metrics`.
208
+ 2. Enable your listener extension.
209
+ 3. Start JupyterLab with the permissive override.
210
+ 4. Trigger a known event, such as a theme change.
211
+ 5. Confirm your server logs or sink receive the matching schema URL.
212
+
213
+ ## Configuration In Practice
214
+
215
+ The frontend dispatcher filter lives in the settings schema in
216
+ [schema/dispatcher.json](./schema/dispatcher.json). The useful keys are:
217
+
218
+ 1. `disabled`: master switch. If this is `true`, nothing is forwarded.
219
+ 2. `anonymous`: if this is `true`, only anonymous events are allowed.
220
+ 3. `sensitivity`: allowed ceiling for event sensitivity.
221
+ 4. `excluded`: per-schema booleans.
222
+
223
+ Configuration precedence is:
224
+
225
+ 1. Built-in defaults.
226
+ 2. JupyterLab user settings.
227
+ 3. Page-config override from `NOTEBOOK_METRICS_OVERRIDE`.
228
+
229
+ The override wins. That is intentional. It lets a deployment operator lock the
230
+ policy down at the server layer.
231
+
232
+ One subtle point matters here: the built-in events are all currently
233
+ non-anonymous and high-sensitivity. If you leave `anonymous: true` or set
234
+ `sensitivity` below `high`, you will suppress the shipped events even when
235
+ `disabled` is `false`.
236
+
237
+ ## Common Mistakes
238
+
239
+ These are the failures most likely to make a first integration feel broken.
240
+
241
+ 1. Installing the package and expecting the default collector to save data.
242
+ 2. Forgetting that the effective startup policy needs `disabled: false`.
243
+ 3. Leaving `anonymous: true`, which filters out the shipped events.
244
+ 4. Leaving `sensitivity` below `high`, which filters out the shipped events.
245
+ 5. Expecting user settings to beat the server-side override file.
246
+ 6. Forgetting to disable `notebook-metrics:collector` after shipping a
247
+ real frontend collector.
248
+ 7. Expecting the backend listener path to work in JupyterLite.
249
+
250
+ ## Where To Read Next
251
+
252
+ If you want the next layer of detail, these files are the most useful starting
253
+ points.
254
+
255
+ 1. [README.md](./README.md) for installation and development workflows.
256
+ 2. [src/plugins.ts](./src/plugins.ts) for the runtime wiring.
257
+ 3. [src/metrics.ts](./src/metrics.ts) for the public TypeScript contract.
258
+ 4. [schema/dispatcher.json](./schema/dispatcher.json) for the settings model.
259
+ 5. [notebook_metrics/**init**.py](./notebook_metrics/__init__.py)
260
+ for server extension behavior and override loading.
261
+ 6. [ui-tests/tests/notebook_metrics.spec.ts](./ui-tests/tests/notebook_metrics.spec.ts)
262
+ for an end-to-end backend collection example.
263
+
264
+ ## A Good First Goal
265
+
266
+ If you are integrating this package for the first time, aim for this before you
267
+ do anything more ambitious:
268
+
269
+ 1. Start JupyterLab with a permissive override.
270
+ 2. Trigger one known event.
271
+ 3. See one matching schema URL in your chosen sink.
272
+
273
+ Once that works, you can tighten the policy, narrow the `excluded` map, or swap
274
+ in a production collector.