@oceanum/oceanumlab 4.7.0 → 4.7.1

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 (59) hide show
  1. package/README.md +102 -11
  2. package/lib/DatameshUI.d.ts +1 -6
  3. package/lib/DatameshUI.js +1 -9
  4. package/lib/DatameshWidget.d.ts +18 -2
  5. package/lib/DatameshWidget.js +70 -11
  6. package/lib/StoredNotebooks.d.ts +16 -0
  7. package/lib/StoredNotebooks.js +84 -0
  8. package/lib/Tabs.d.ts +26 -0
  9. package/lib/Tabs.js +45 -0
  10. package/lib/aiBackend.d.ts +1 -1
  11. package/lib/aiBackend.js +1 -1
  12. package/lib/auth/accountWidget.d.ts +35 -0
  13. package/lib/auth/accountWidget.js +95 -0
  14. package/lib/auth/claims.d.ts +3 -0
  15. package/lib/auth/claims.js +25 -0
  16. package/lib/auth/plugin.d.ts +9 -0
  17. package/lib/auth/plugin.js +126 -0
  18. package/lib/auth/serverAuth.d.ts +79 -0
  19. package/lib/auth/serverAuth.js +253 -0
  20. package/lib/auth/tokens.d.ts +95 -0
  21. package/lib/auth/tokens.js +9 -0
  22. package/lib/chatRouter.d.ts +13 -1
  23. package/lib/chatRouter.js +9 -2
  24. package/lib/constants.d.ts +1 -1
  25. package/lib/constants.js +1 -1
  26. package/lib/datameshUiUrl.d.ts +2 -2
  27. package/lib/datameshUiUrl.js +6 -6
  28. package/lib/index.d.ts +8 -1
  29. package/lib/index.js +56 -17
  30. package/lib/specStore.d.ts +37 -0
  31. package/lib/specStore.js +57 -0
  32. package/package.json +9 -1
  33. package/schema/datamesh-connect.json +3 -12
  34. package/src/DatameshUI.tsx +1 -10
  35. package/src/DatameshWidget.tsx +124 -19
  36. package/src/StoredNotebooks.tsx +166 -0
  37. package/src/Tabs.tsx +102 -0
  38. package/src/__tests__/DatameshWidget.spec.tsx +33 -20
  39. package/src/__tests__/OceanumPanel.spec.tsx +101 -0
  40. package/src/__tests__/Tabs.spec.tsx +168 -0
  41. package/src/__tests__/aiBackend.spec.ts +5 -6
  42. package/src/__tests__/chatRouter.spec.ts +9 -15
  43. package/src/__tests__/datameshUiUrl.spec.ts +4 -3
  44. package/src/__tests__/pluginArity.spec.ts +24 -0
  45. package/src/__tests__/serverAuth.spec.ts +277 -0
  46. package/src/__tests__/settingsSchema.spec.ts +73 -0
  47. package/src/__tests__/specStore.spec.ts +126 -0
  48. package/src/aiBackend.ts +1 -1
  49. package/src/auth/accountWidget.ts +113 -0
  50. package/src/auth/claims.ts +30 -0
  51. package/src/auth/plugin.ts +155 -0
  52. package/src/auth/serverAuth.ts +341 -0
  53. package/src/auth/tokens.ts +104 -0
  54. package/src/chatRouter.ts +8 -4
  55. package/src/constants.ts +1 -1
  56. package/src/datameshUiUrl.ts +6 -6
  57. package/src/index.ts +85 -36
  58. package/src/specStore.ts +82 -0
  59. package/style/index.css +158 -0
@@ -0,0 +1,95 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ import { ISignal } from '@lumino/signaling';
3
+ /**
4
+ * One Oceanum deployment (production or development).
5
+ */
6
+ export interface IOceanumEnvironment {
7
+ /** Page hostnames served by this environment, e.g. `notebook.oceanum.io`. */
8
+ readonly hosts: readonly string[];
9
+ /** Auth0 tenant domain, e.g. `auth.oceanum.io`. */
10
+ readonly auth0Domain: string;
11
+ /** Auth0 SPA client id. */
12
+ readonly clientId: string;
13
+ /**
14
+ * The Oceanum apps domain, e.g. `oceanum.io`. The Oceanum nav builds its app, account,
15
+ * docs and sign-out URLs from it (`https://platform.oceanum.io/account`, ...).
16
+ */
17
+ readonly oceanumDomain: string;
18
+ /** Service base URLs for this environment. */
19
+ readonly urls: IOceanumServiceUrls;
20
+ /**
21
+ * Whether to check for an existing Oceanum.io session with a full-page redirect when the
22
+ * silent iframe check finds none. Only for a tenant on another site than the notebook
23
+ * (browsers that block third-party cookies hide the session from the iframe), and only where
24
+ * the site origin is an allowed callback URL.
25
+ */
26
+ readonly signInRedirect: boolean;
27
+ }
28
+ /** Service base URLs for an environment, without trailing slashes. */
29
+ export interface IOceanumServiceUrls {
30
+ /** Datamesh gateway, e.g. `https://datamesh.oceanum.io`. */
31
+ readonly datamesh: string;
32
+ /** Spec store, e.g. `https://specs.oceanum.io`. */
33
+ readonly specs: string;
34
+ /** User management service (Auth0 user metadata), e.g. `https://manage.oceanum.io`. */
35
+ readonly manage: string;
36
+ /**
37
+ * Datamesh UI, e.g. `https://ui.datamesh.oceanum.io`, shown by oceanumlab's Datamesh panel.
38
+ * Optional: without it, Oceanum production applies.
39
+ */
40
+ readonly datameshUi?: string;
41
+ /**
42
+ * Oceanum AI backend, e.g. `https://ai.oceanum.io`, called by oceanumlab's AI chat.
43
+ * Optional: without it, Oceanum production applies.
44
+ */
45
+ readonly ai?: string;
46
+ }
47
+ export type ColorScheme = 'light' | 'dark' | 'auto';
48
+ /** The signed-in user, from the Auth0 ID token claims. */
49
+ export interface IOceanumUser {
50
+ readonly sub: string;
51
+ readonly email: string;
52
+ readonly name: string | null;
53
+ /** `https://oceanum.io/active_org`. */
54
+ readonly activeOrg: string | null;
55
+ /** `https://oceanum.io/color_scheme`. */
56
+ readonly colorScheme: ColorScheme | null;
57
+ }
58
+ /**
59
+ * Oceanum.io sign-in state, shared with other Oceanum Notebook extensions.
60
+ */
61
+ export interface IOceanumAuth {
62
+ /** The environment for this page, or `null` if sign-in is not configured for this host. */
63
+ readonly environment: IOceanumEnvironment | null;
64
+ /** Service URLs for `environment`, or `null` when it is `null`. */
65
+ readonly urls: IOceanumServiceUrls | null;
66
+ /** Resolves once any login redirect has been handled and the session restored. */
67
+ readonly ready: Promise<void>;
68
+ /** The signed-in user, or `null`. */
69
+ readonly user: IOceanumUser | null;
70
+ /** Emits the new user (or `null`) on sign-in and sign-out. */
71
+ readonly userChanged: ISignal<IOceanumAuth, IOceanumUser | null>;
72
+ /** Emits the new access token (or `null`) whenever it changes, including refreshes. */
73
+ readonly tokenChanged: ISignal<IOceanumAuth, string | null>;
74
+ /**
75
+ * Start sign-in without leaving the page, so its kernels survive. Resolves once sign-in has
76
+ * started, not when it completes; watch `userChanged` for the result. Does nothing while a
77
+ * user is signed in.
78
+ */
79
+ signIn(): Promise<void>;
80
+ /** Sign out of Oceanum.io and clear the session. */
81
+ signOut(): Promise<void>;
82
+ /**
83
+ * A current access token, refreshed if it is near expiry, or `null` when signed out.
84
+ * Never cache the result: Auth0 access tokens are short-lived.
85
+ */
86
+ getAccessToken(): Promise<string | null>;
87
+ }
88
+ /**
89
+ * The sign-in contract. oceanumlab owns it and exports it from its package entry, because a
90
+ * Lumino token is matched by object identity: every extension that provides or consumes it
91
+ * has to import this one object, which JupyterLab arranges by sharing `@oceanum/oceanumlab`
92
+ * as a singleton module. On an ordinary JupyterLab oceanumlab's own device sign-in provides
93
+ * it; on notebook.oceanum.io the notebook's Oceanum widget extension does.
94
+ */
95
+ export declare const IOceanumAuth: Token<IOceanumAuth>;
@@ -0,0 +1,9 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ /**
3
+ * The sign-in contract. oceanumlab owns it and exports it from its package entry, because a
4
+ * Lumino token is matched by object identity: every extension that provides or consumes it
5
+ * has to import this one object, which JupyterLab arranges by sharing `@oceanum/oceanumlab`
6
+ * as a singleton module. On an ordinary JupyterLab oceanumlab's own device sign-in provides
7
+ * it; on notebook.oceanum.io the notebook's Oceanum widget extension does.
8
+ */
9
+ export const IOceanumAuth = new Token('@oceanum/oceanumlab:IOceanumAuth', 'Oceanum.io sign-in state and Datamesh access tokens.');
@@ -39,6 +39,12 @@ export declare class ChatRouter {
39
39
  private _settings;
40
40
  private _commands;
41
41
  private _notebook;
42
+ /**
43
+ * The Oceanum AI backend's address: the signed-in deployment's, or empty for the
44
+ * production default. Deployment configuration, not a setting -- a user-editable
45
+ * address is somewhere to send the user's credential.
46
+ */
47
+ private _backend;
42
48
  /**
43
49
  * @param _commands Where the host's Oceanum.io sign-in is asked for, when
44
50
  * there is no pasted Datamesh token.
@@ -47,7 +53,13 @@ export declare class ChatRouter {
47
53
  * every request, so it follows the conversation rather than whichever tab
48
54
  * happens to be active.
49
55
  */
50
- constructor(_settings: ISettingRegistry.ISettings, _commands: AuthCommands, _notebook: () => Promise<INotebookSnapshot | null>);
56
+ constructor(_settings: ISettingRegistry.ISettings, _commands: AuthCommands, _notebook: () => Promise<INotebookSnapshot | null>,
57
+ /**
58
+ * The Oceanum AI backend's address: the signed-in deployment's, or empty for the
59
+ * production default. Deployment configuration, not a setting -- a user-editable
60
+ * address is somewhere to send the user's credential.
61
+ */
62
+ _backend?: () => string);
51
63
  route(prompt: string, chatHistory?: ChatMessage[], signal?: AbortSignal, onProgress?: (progress: Progress) => void): Promise<RouteResult>;
52
64
  /**
53
65
  * The notebook half of the execute loop: the code ran in the user's own
package/lib/chatRouter.js CHANGED
@@ -38,10 +38,17 @@ export class ChatRouter {
38
38
  * every request, so it follows the conversation rather than whichever tab
39
39
  * happens to be active.
40
40
  */
41
- constructor(_settings, _commands, _notebook) {
41
+ constructor(_settings, _commands, _notebook,
42
+ /**
43
+ * The Oceanum AI backend's address: the signed-in deployment's, or empty for the
44
+ * production default. Deployment configuration, not a setting -- a user-editable
45
+ * address is somewhere to send the user's credential.
46
+ */
47
+ _backend = () => '') {
42
48
  this._settings = _settings;
43
49
  this._commands = _commands;
44
50
  this._notebook = _notebook;
51
+ this._backend = _backend;
45
52
  }
46
53
  async route(prompt, chatHistory = [], signal, onProgress) {
47
54
  const { payload, isCodeCell, context } = await this._gather(prompt, chatHistory);
@@ -110,7 +117,7 @@ export class ChatRouter {
110
117
  ? 'Not signed in. Sign in to Oceanum.io to use Oceanum AI, or set a Datamesh token in Settings → Oceanum.io.'
111
118
  : 'Datamesh token not configured. Set your token in Settings → Oceanum.io.');
112
119
  }
113
- const backend = aiBackendUrl(this._settings.get('aiBackendUrl').composite);
120
+ const backend = aiBackendUrl(this._backend());
114
121
  let response;
115
122
  try {
116
123
  response = await fetch(`${backend}${path}`, {
@@ -2,7 +2,7 @@
2
2
  * Build-time constants for Oceanum AI integration.
3
3
  * These values are compiled into the extension.
4
4
  */
5
- /** Default URL of the Oceanum AI backend service (the `aiBackendUrl` setting) */
5
+ /** Default URL of the Oceanum AI backend service, where the deployment names none. */
6
6
  export declare const OCEANUM_AI_BACKEND_URL = "https://ai.oceanum.io";
7
7
  /** URL of the Datamesh UI embedded in the Datamesh panel */
8
8
  export declare const DATAMESH_UI_SERVICE = "https://ui.datamesh.oceanum.io";
package/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Build-time constants for Oceanum AI integration.
3
3
  * These values are compiled into the extension.
4
4
  */
5
- /** Default URL of the Oceanum AI backend service (the `aiBackendUrl` setting) */
5
+ /** Default URL of the Oceanum AI backend service, where the deployment names none. */
6
6
  export const OCEANUM_AI_BACKEND_URL = 'https://ai.oceanum.io';
7
7
  /** URL of the Datamesh UI embedded in the Datamesh panel */
8
8
  export const DATAMESH_UI_SERVICE = 'https://ui.datamesh.oceanum.io';
@@ -1,8 +1,8 @@
1
- /** The Datamesh UI address to use for a `datameshUiUrl` setting value. */
1
+ /** The Datamesh UI address to use for a configured value. */
2
2
  export declare function validDatameshUiUrl(url: string | null | undefined): URL;
3
3
  /**
4
4
  * The panel's iframe `src`: the Datamesh UI in embed mode, which hides the
5
- * Oceanum.io navigator. Any query the setting already has is kept.
5
+ * Oceanum.io navigator. Any query the address already has is kept.
6
6
  */
7
7
  export declare function datameshUiSrc(url: string | null | undefined): string;
8
8
  /**
@@ -1,20 +1,20 @@
1
1
  /**
2
2
  * The Datamesh UI panel's address, and which messages may come from it.
3
3
  *
4
- * The address is a setting (`datameshUiUrl`), so a deployment can point the
5
- * panel at another Datamesh UI -- a dev one, say. A setting is typed in by
6
- * hand, so anything that is not an http(s) URL falls back to the default
7
- * rather than ending up in an iframe's `src`.
4
+ * The address comes from the deployment's sign-in environment, so a development
5
+ * deployment can point the panel at its own Datamesh UI. It is still written by a
6
+ * person in a config file, so anything that is not an http(s) URL falls back to the
7
+ * default rather than ending up in an iframe's `src`.
8
8
  */
9
9
  import { DATAMESH_UI_SERVICE } from './constants';
10
10
  import { validHttpUrl } from './httpUrl';
11
- /** The Datamesh UI address to use for a `datameshUiUrl` setting value. */
11
+ /** The Datamesh UI address to use for a configured value. */
12
12
  export function validDatameshUiUrl(url) {
13
13
  return validHttpUrl(url, DATAMESH_UI_SERVICE);
14
14
  }
15
15
  /**
16
16
  * The panel's iframe `src`: the Datamesh UI in embed mode, which hides the
17
- * Oceanum.io navigator. Any query the setting already has is kept.
17
+ * Oceanum.io navigator. Any query the address already has is kept.
18
18
  */
19
19
  export function datameshUiSrc(url) {
20
20
  const src = validDatameshUiUrl(url);
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  import { LabIcon } from '@jupyterlab/ui-components';
3
+ import { IOceanumAuth } from './auth/tokens';
3
4
  import '../style/index.css';
4
5
  export declare const oceanumIcon: LabIcon;
5
6
  /**
@@ -12,5 +13,11 @@ export declare const datamesh_connect_extension: JupyterFrontEndPlugin<void>;
12
13
  * `oceanum-ai:submit-prompt` command.
13
14
  */
14
15
  export declare const oceanum_ai_extension: JupyterFrontEndPlugin<void>;
15
- declare const _default: JupyterFrontEndPlugin<void>[];
16
+ /**
17
+ * The sign-in contract, exported so another extension can provide or consume it: a Lumino
18
+ * token is matched by object identity, so everyone has to import this one object, and
19
+ * JupyterLab shares `@oceanum/oceanumlab` between extensions as a singleton module.
20
+ */
21
+ export * from './auth/tokens';
22
+ declare const _default: (JupyterFrontEndPlugin<IOceanumAuth> | JupyterFrontEndPlugin<void>)[];
16
23
  export default _default;
package/lib/index.js CHANGED
@@ -12,6 +12,8 @@ import { requestAPI } from './handler';
12
12
  import { ChatRouter, ChatRouterError } from './chatRouter';
13
13
  import { reporterFor } from './progress';
14
14
  import { ConversationPin } from './conversationPin';
15
+ import { deviceAuthPlugin } from './auth/plugin';
16
+ import { IOceanumAuth } from './auth/tokens';
15
17
  import { snapshotFromIpynb, snapshotOf } from './notebookContext';
16
18
  import { notebookHost } from './notebookHost';
17
19
  import { KernelHandoff } from './kernelHandoff';
@@ -38,13 +40,19 @@ export const datamesh_connect_extension = {
38
40
  ISettingRegistry,
39
41
  IStateDB
40
42
  ],
41
- activate: (app, palette, restorer, status, settingRegistry) => {
43
+ // Optional so the panel still loads on a host with no Oceanum environment; the
44
+ // Notebooks tab then explains why it is empty instead of the panel failing to start.
45
+ optional: [IOceanumAuth],
46
+ activate: (app, palette, restorer, status, settingRegistry, stateDB, auth) => {
47
+ var _a, _b, _c, _d;
42
48
  console.log('Oceanum datamesh connect extension is loaded');
43
- // The `datameshUiUrl` setting; the default until the settings load.
44
- let datameshUiUrl = DATAMESH_UI_SERVICE;
45
- // The `aiBackendUrl` and `datameshToken` settings for the AI chat; the
46
- // defaults until the settings load.
47
- let aiBackendUrl = OCEANUM_AI_BACKEND_URL;
49
+ // Where the Datamesh UI and Oceanum AI live is the deployment's business, so it comes
50
+ // from the sign-in environment (the Jupyter server's configuration, or the notebook
51
+ // site's) and falls back to production. These were user settings until 4.7.1; a
52
+ // user-editable service address is somewhere to send the user's credential.
53
+ const datameshUiUrl = (_b = (_a = auth === null || auth === void 0 ? void 0 : auth.urls) === null || _a === void 0 ? void 0 : _a.datameshUi) !== null && _b !== void 0 ? _b : DATAMESH_UI_SERVICE;
54
+ const aiBackendUrl = (_d = (_c = auth === null || auth === void 0 ? void 0 : auth.urls) === null || _c === void 0 ? void 0 : _c.ai) !== null && _d !== void 0 ? _d : OCEANUM_AI_BACKEND_URL;
55
+ // The `datameshToken` setting for the AI chat; empty until the settings load.
48
56
  let datameshTokenSetting = '';
49
57
  // Tells the sidebar the settings above have changed.
50
58
  const settingsChanged = new Signal(app);
@@ -60,14 +68,7 @@ export const datamesh_connect_extension = {
60
68
  }).then(res => console.log(res));
61
69
  }
62
70
  window.injectToken = set.get('injectToken').user;
63
- aiBackendUrl = set.get('aiBackendUrl').composite;
64
71
  datameshTokenSetting = set.get('datameshToken').composite;
65
- // An open Datamesh UI panel follows a change of address.
66
- datameshUiUrl = set.get('datameshUiUrl').composite;
67
- const datameshUI = findDatameshUI();
68
- if (datameshUI) {
69
- datameshUI.url = datameshUiUrl;
70
- }
71
72
  settingsChanged.emit();
72
73
  };
73
74
  //Try to get the datamesh token from the envars
@@ -109,14 +110,41 @@ export const datamesh_connect_extension = {
109
110
  aiBackendUrl: () => aiBackendUrl,
110
111
  settingsChanged,
111
112
  commands: app.commands,
112
- getCurrentWidget
113
+ getCurrentWidget,
114
+ auth
115
+ // No `openStoredNotebook`: nothing can open a spec store record by id yet.
116
+ // share-oceanum has `oceanum-share:open`, but it takes no arguments — it opens
117
+ // its own picker — so there is nothing to hand an id to. Opening arrives with
118
+ // the drive in OCE-182, where the contents API makes it the ordinary path.
119
+ // Until then the list reads rather than offering a click that does nothing.
113
120
  });
114
121
  datameshConnectWidget.id = 'datamesh-connect';
115
122
  datameshConnectWidget.title.icon = oceanumIcon;
116
123
  datameshConnectWidget.title.caption = 'Datamesh Connect';
117
124
  restorer.add(datameshConnectWidget, 'datamesh-connect');
125
+ // The layout restorer restores the widget's place in the shell, not fields on it,
126
+ // so the chosen tab is kept here. Restoring is best effort: a missing or unreadable
127
+ // entry just leaves the default.
128
+ const TAB_STATE_KEY = `${PLUGIN_ID}:tab`;
129
+ void stateDB
130
+ .fetch(TAB_STATE_KEY)
131
+ .then(value => {
132
+ if (typeof value === 'string') {
133
+ datameshConnectWidget.selectTab(value);
134
+ }
135
+ })
136
+ .catch(() => undefined);
137
+ datameshConnectWidget.tabChanged.connect((_, id) => {
138
+ void stateDB.save(TAB_STATE_KEY, id);
139
+ });
118
140
  // Rank has been chosen somewhat arbitrarily to give priority to the running
119
141
  // sessions widget in the sidebar.
142
+ //
143
+ // Oceanum Notebook wants this panel first instead, but that is a property of that
144
+ // distribution rather than of the extension: in a plain JupyterLab the file browser
145
+ // is the primary navigation surface and displacing it would be a regression. A
146
+ // distribution moves it with JupyterLab's own shell user-layout settings, keyed on
147
+ // this widget's id, rather than this extension hard-coding one host's preference.
120
148
  app.shell.add(datameshConnectWidget, 'left', { rank: 900 });
121
149
  app.commands.addCommand('datamesh-ui:open', {
122
150
  execute: (args) => {
@@ -134,7 +162,8 @@ export const oceanum_ai_extension = {
134
162
  id: '@oceanum/oceanumlab:ai-chat',
135
163
  autoStart: true,
136
164
  requires: [INotebookTracker, ISettingRegistry],
137
- activate: (app, notebookTracker, settingRegistry) => {
165
+ optional: [IOceanumAuth],
166
+ activate: (app, notebookTracker, settingRegistry, auth) => {
138
167
  console.log('Oceanum AI chat extension is loaded');
139
168
  const SETTINGS_ID = '@oceanum/oceanumlab:datamesh-connect';
140
169
  settingRegistry
@@ -170,7 +199,7 @@ export const oceanum_ai_extension = {
170
199
  // Gone: an answer's cells will go into a new notebook instead.
171
200
  return null;
172
201
  }
173
- });
202
+ }, () => { var _a, _b; return (_b = (_a = auth === null || auth === void 0 ? void 0 : auth.urls) === null || _a === void 0 ? void 0 : _a.ai) !== null && _b !== void 0 ? _b : ''; });
174
203
  // Asked only when an answer has blocks to place: brings the
175
204
  // conversation's notebook to the front, opening it again if closed.
176
205
  const handoff = new KernelHandoff(() => pin.show(), () => readFrom);
@@ -290,4 +319,14 @@ export const oceanum_ai_extension = {
290
319
  });
291
320
  }
292
321
  };
293
- export default [datamesh_connect_extension, oceanum_ai_extension];
322
+ /**
323
+ * The sign-in contract, exported so another extension can provide or consume it: a Lumino
324
+ * token is matched by object identity, so everyone has to import this one object, and
325
+ * JupyterLab shares `@oceanum/oceanumlab` between extensions as a singleton module.
326
+ */
327
+ export * from './auth/tokens';
328
+ export default [
329
+ deviceAuthPlugin,
330
+ datamesh_connect_extension,
331
+ oceanum_ai_extension
332
+ ];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Reading the Oceanum spec store's `notebook` records.
3
+ *
4
+ * Deliberately small: only what the Stored notebooks tab needs. oceanum-notebook's
5
+ * share-oceanum package has a fuller client (create, update, permissions), and when
6
+ * sharing moves here the two should become one rather than sitting side by side.
7
+ */
8
+ /** A spec store list entry. The list omits the notebook body. */
9
+ export interface ISpecSummary {
10
+ id: string;
11
+ name: string;
12
+ description: string | null;
13
+ /** Set when the record is created and never updated, so this is a creation time. */
14
+ modified: string;
15
+ /** Null unless the caller created the record: the store hides other creators. */
16
+ creator: string | null;
17
+ }
18
+ /**
19
+ * Every notebook the signed-in user may read: their own and those shared with them.
20
+ *
21
+ * The store answers an unauthenticated caller with an empty list rather than an error,
22
+ * so a missing token is indistinguishable from an empty account — callers should check
23
+ * they are signed in before showing "no notebooks".
24
+ */
25
+ export declare function listNotebooks(specsUrl: string, token: string, fetcher?: typeof fetch): Promise<ISpecSummary[]>;
26
+ /**
27
+ * Split a listing into the caller's own notebooks and the rest, newest first.
28
+ *
29
+ * The store only sends `creator` on records the caller created, so anything else it
30
+ * returns is shared with them — directly, or because it is public. Addresses are
31
+ * compared case-insensitively: the store matches entities as case-sensitive globs, so
32
+ * the two sides can differ in case.
33
+ */
34
+ export declare function partitionNotebooks(items: readonly ISpecSummary[], email: string | null): {
35
+ mine: ISpecSummary[];
36
+ shared: ISpecSummary[];
37
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Reading the Oceanum spec store's `notebook` records.
3
+ *
4
+ * Deliberately small: only what the Stored notebooks tab needs. oceanum-notebook's
5
+ * share-oceanum package has a fuller client (create, update, permissions), and when
6
+ * sharing moves here the two should become one rather than sitting side by side.
7
+ */
8
+ function isSummary(value) {
9
+ if (typeof value !== 'object' || value === null) {
10
+ return false;
11
+ }
12
+ const v = value;
13
+ return (typeof v.id === 'string' &&
14
+ typeof v.name === 'string' &&
15
+ typeof v.modified === 'string');
16
+ }
17
+ /**
18
+ * Every notebook the signed-in user may read: their own and those shared with them.
19
+ *
20
+ * The store answers an unauthenticated caller with an empty list rather than an error,
21
+ * so a missing token is indistinguishable from an empty account — callers should check
22
+ * they are signed in before showing "no notebooks".
23
+ */
24
+ export async function listNotebooks(specsUrl, token, fetcher = fetch) {
25
+ const url = `${specsUrl.replace(/\/+$/, '')}/specs/notebook`;
26
+ const response = await fetcher(url, {
27
+ headers: { Authorization: `Bearer ${token}` }
28
+ });
29
+ if (!response.ok) {
30
+ // The store answers a bad or expired token with 400, and 403 both for no access
31
+ // and for a record that does not exist.
32
+ throw new Error(`Oceanum.io returned HTTP ${response.status}.`);
33
+ }
34
+ const data = await response.json();
35
+ if (!Array.isArray(data)) {
36
+ throw new Error('Oceanum.io returned an unexpected response.');
37
+ }
38
+ return data.filter(isSummary);
39
+ }
40
+ /**
41
+ * Split a listing into the caller's own notebooks and the rest, newest first.
42
+ *
43
+ * The store only sends `creator` on records the caller created, so anything else it
44
+ * returns is shared with them — directly, or because it is public. Addresses are
45
+ * compared case-insensitively: the store matches entities as case-sensitive globs, so
46
+ * the two sides can differ in case.
47
+ */
48
+ export function partitionNotebooks(items, email) {
49
+ var _a;
50
+ const me = (_a = email === null || email === void 0 ? void 0 : email.toLowerCase()) !== null && _a !== void 0 ? _a : null;
51
+ const sorted = [...items].sort((a, b) => b.modified.localeCompare(a.modified));
52
+ const isMine = (item) => { var _a; return me !== null && ((_a = item.creator) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === me; };
53
+ return {
54
+ mine: sorted.filter(isMine),
55
+ shared: sorted.filter(item => !isMine(item))
56
+ };
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oceanum/oceanumlab",
3
- "version": "4.7.0",
3
+ "version": "4.7.1",
4
4
  "description": "A Jupyterlab extension to interact with the Oceanum.io platform",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -60,8 +60,16 @@
60
60
  "@jupyterlab/application": "^4.6.3",
61
61
  "@jupyterlab/apputils": "^4.7.3",
62
62
  "@jupyterlab/cells": "^4.6.3",
63
+ "@jupyterlab/console": "^4.6.3",
64
+ "@jupyterlab/coreutils": "^6.6.3",
63
65
  "@jupyterlab/notebook": "^4.6.3",
66
+ "@jupyterlab/services": "^7.6.3",
64
67
  "@jupyterlab/settingregistry": "^4.6.3",
68
+ "@lumino/commands": "^2.3.3",
69
+ "@lumino/coreutils": "^2.2.2",
70
+ "@lumino/disposable": "^2.1.5",
71
+ "@lumino/signaling": "^2.1.5",
72
+ "@lumino/widgets": "^2.8.0",
65
73
  "@phosphor-icons/react": "^2.1.10",
66
74
  "marked": "^18.0.13"
67
75
  },
@@ -18,18 +18,6 @@
18
18
  "description": "Inject Datamesh token into code.",
19
19
  "default": false
20
20
  },
21
- "datameshUiUrl": {
22
- "type": "string",
23
- "title": "Datamesh UI URL",
24
- "description": "Address of the Datamesh UI shown in the Datamesh panel. Use the address it is finally served from: the panel only accepts workspace updates from this address's origin, so an address that redirects to another origin will not update the workspace.",
25
- "default": "https://ui.datamesh.oceanum.io"
26
- },
27
- "aiBackendUrl": {
28
- "type": "string",
29
- "title": "Oceanum AI URL",
30
- "description": "Address of the Oceanum AI backend used by the AI chat.",
31
- "default": "https://ai.oceanum.io"
32
- },
33
21
  "autoRunCode": {
34
22
  "type": "boolean",
35
23
  "title": "Auto-run code",
@@ -43,5 +31,8 @@
43
31
  "default": false
44
32
  }
45
33
  },
34
+ "patternProperties": {
35
+ "^(datameshUiUrl|aiBackendUrl)$": {}
36
+ },
46
37
  "additionalProperties": false
47
38
  }
@@ -3,20 +3,11 @@ import { ReactWidget } from '@jupyterlab/apputils';
3
3
  import { datameshUiSrc } from './datameshUiUrl';
4
4
 
5
5
  export class DatameshUI extends ReactWidget {
6
- /** `url` is the `datameshUiUrl` setting. */
6
+ /** `url` is the deployment's Datamesh UI address (the sign-in environment's). */
7
7
  constructor(private _url: string) {
8
8
  super();
9
9
  }
10
10
 
11
- /**
12
- * Point the panel at the `datameshUiUrl` setting's new value. The iframe
13
- * reloads only if its address actually changes.
14
- */
15
- set url(url: string) {
16
- this._url = url;
17
- this.update();
18
- }
19
-
20
11
  /** The iframe's window, for telling its messages apart; `null` if none. */
21
12
  get frame(): Window | null {
22
13
  return this.node.querySelector('iframe')?.contentWindow ?? null;