diff --git a/package.json b/package.json index 7061edc25e6e..355d6567df26 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,8 @@ "yjs": "^13.5.40" }, "dependencies": { + "@jupyterlab/application": "workspace:^", + "@jupyterlab/apputils": "workspace:^", "@typescript-eslint/eslint-plugin": "~6.13.2", "@typescript-eslint/parser": "~6.13.2", "eslint": "~8.55.0", diff --git a/packages/apputils/src/sessioncontext.tsx b/packages/apputils/src/sessioncontext.tsx index 3f4e23f03d16..8a8b9d0a08a5 100644 --- a/packages/apputils/src/sessioncontext.tsx +++ b/packages/apputils/src/sessioncontext.tsx @@ -1,7 +1,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { IChangedArgs, PathExt } from '@jupyterlab/coreutils'; +import { IChangedArgs, PageConfig, PathExt } from '@jupyterlab/coreutils'; import { Kernel, KernelMessage, @@ -16,12 +16,20 @@ import { TranslationBundle } from '@jupyterlab/translation'; import { find } from '@lumino/algorithm'; -import { JSONExt, PromiseDelegate, UUID } from '@lumino/coreutils'; +import { + JSONExt, + PartialJSONObject, + PartialJSONValue, + PromiseDelegate, + UUID +} from '@lumino/coreutils'; import { IDisposable, IObservableDisposable } from '@lumino/disposable'; import { ISignal, Signal } from '@lumino/signaling'; import { Widget } from '@lumino/widgets'; import * as React from 'react'; import { Dialog, showDialog } from './dialog'; +import { DialogWidget } from '@jupyterlab/ui-components'; +import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; /** * A context object to manage a widget's kernel session connection. @@ -297,6 +305,10 @@ export namespace ISessionContext { */ readonly autoStartDefault?: boolean; + /** + * Kernel custom specs defined by kernel name + */ + customKernelSpecs?: undefined | PartialJSONObject; /** * Skip showing the kernel restart dialog if checked (default `false`). */ @@ -694,7 +706,8 @@ export class SessionContext implements ISessionContext { */ async startKernel(): Promise { const preference = this.kernelPreference; - + const specs = this.specsManager.specs; + // if (!preference.autoStartDefault && preference.shouldStart === false) { return true; } @@ -704,12 +717,19 @@ export class SessionContext implements ISessionContext { options = { id: preference.id }; } else { const name = Private.getDefaultKernel({ - specs: this.specsManager.specs, + specs, sessions: this.sessionManager.running(), preference }); if (name) { - options = { name }; + if (preference.customKernelSpecs) { + options = { + name, + custom_kernel_specs: preference.customKernelSpecs + }; + } else { + options = { name }; + } } } @@ -733,6 +753,7 @@ export class SessionContext implements ISessionContext { */ async restartKernel(): Promise { const kernel = this.session?.kernel || null; + if (this._isRestarting) { return; } @@ -1407,19 +1428,37 @@ export class SessionContextDialogs implements ISessionContext.IDialogs { return; } - if (hasCheckbox && result.isChecked !== null) { - sessionContext.kernelPreference = { - ...sessionContext.kernelPreference, - autoStartDefault: result.isChecked + const dialogResult = result.value as Kernel.IModel; + + if (dialogResult) { + let model = { + name: dialogResult.name, + custom_kernel_specs: {} }; - } - const model = result.value; - if (model === null && !sessionContext.hasNoKernel) { - return sessionContext.shutdown(); - } - if (model) { - await sessionContext.changeKernel(model); + if (hasCheckbox && result.isChecked !== null) { + if (model && sessionContext.kernelPreference?.customKernelSpecs) { + sessionContext.kernelPreference.customKernelSpecs = undefined; + } + + if (model && dialogResult.custom_kernel_specs) { + sessionContext.kernelPreference.customKernelSpecs = + dialogResult.custom_kernel_specs; + model['custom_kernel_specs'] = dialogResult.custom_kernel_specs; + } + + sessionContext.kernelPreference = { + ...sessionContext.kernelPreference, + autoStartDefault: result.isChecked + }; + } + + if (model === null && !sessionContext.hasNoKernel) { + return sessionContext.shutdown(); + } + if (model) { + await sessionContext.changeKernel(model); + } } } @@ -1813,20 +1852,36 @@ namespace Private { sessionContext: ISessionContext, translator?: ITranslator ) => - new KernelSelector({ - node: createSelectorNode(sessionContext, translator) - }); + new KernelSelector(sessionContext, translator); /** * A widget that provides a kernel selection. */ - class KernelSelector extends Widget { + export class KernelSelector extends Widget { + sessionContext: ISessionContext; + translator: ITranslator | undefined; + /** + * Create a new kernel selector widget. + */ + constructor(sessionContext: ISessionContext, translator?: ITranslator) { + super({ node: createSelectorNode(sessionContext, translator) }); + this.sessionContext = sessionContext; + this.translator = translator; + } + /** * Get the value of the kernel selector widget. */ getValue(): Kernel.IModel { - const selector = this.node.querySelector('select') as HTMLSelectElement; - return JSON.parse(selector.value) as Kernel.IModel; + const selector = this.node.querySelector( + 'select#js-kernel-selector' + ) as HTMLSelectElement; + const selectorKernelSpecs = selector.getAttribute('data-kernel-spec'); + let kernelData = JSON.parse(selector.value) as Kernel.IModel; + if (selectorKernelSpecs) { + kernelData['custom_kernel_specs'] = JSON.parse(selectorKernelSpecs); + } + return kernelData; } } @@ -1842,11 +1897,24 @@ namespace Private { const trans = translator.load('jupyterlab'); const body = document.createElement('div'); + + const container = document.createElement('div'); + + const kernelSpecsContainer = document.createElement('div'); + + container.setAttribute('id', 'js-kernel-select-container'); + + kernelSpecsContainer.setAttribute( + 'id', + 'js-kernel-specs-select-container' + ); + const text = document.createElement('label'); text.textContent = `${trans.__('Select kernel for:')} "${ sessionContext.name }"`; - body.appendChild(text); + + container.appendChild(text); const select = document.createElement('select'); const options = SessionContextDialogs.kernelOptions( @@ -1860,7 +1928,15 @@ namespace Private { optgroup.label = label; for (const { selected, text, title, value } of options) { const option = document.createElement('option'); - if (selected) option.selected = true; + if (selected) { + option.selected = true; + let val = JSON.parse(value); + let id = val && val.id ? val.id : ''; + if (!id) { + select.setAttribute('data-kernel-spec', ''); + checkCustomKernelSpecs(sessionContext, select, trans); + } + } if (title) option.title = title; option.text = text; option.value = value; @@ -1868,10 +1944,104 @@ namespace Private { } select.appendChild(optgroup); } + select.setAttribute('id', 'js-kernel-selector'); + select.onchange = () => { + select.setAttribute('data-kernel-spec', ''); + checkCustomKernelSpecs(sessionContext, select, trans); + }; + body.appendChild(select); + body.appendChild(kernelSpecsContainer); return body; } + function checkCustomKernelSpecs( + sessionContext: ISessionContext, + select: HTMLSelectElement, + trans: IRenderMime.TranslationBundle, + kernelSpeccSelectorContainer?: HTMLDivElement + ) { + let kernelConfiguration: PartialJSONObject = {}; + let selectedKernel = JSON.parse(select.value) as Kernel.IModel; + + let kernelSpecsContainer = document.querySelector( + '#js-kernel-specs-select-container' + ) as HTMLElement; + + if (!kernelSpecsContainer && kernelSpeccSelectorContainer) { + kernelSpecsContainer = kernelSpeccSelectorContainer; + } + + kernelSpecsContainer.innerHTML = ''; + let kernelName = + selectedKernel && selectedKernel.name ? selectedKernel.name : ''; + let kernel = + kernelName && sessionContext.specsManager.specs?.kernelspecs[kernelName]; + const allowInsecureKernelspecParams = + PageConfig.getOption('allow_insecure_kernelspec_params') === 'true' + ? true + : false; + if ( + (kernel && + kernel?.metadata && + kernel?.metadata?.is_secure && + kernel?.metadata?.parameters) || + allowInsecureKernelspecParams + ) { + if (kernel && kernel?.metadata && kernel?.metadata?.parameters) { + let kernelParameters = kernel?.metadata + ?.parameters as PartialJSONObject; + + if (kernelParameters) { + if (sessionContext.kernelPreference?.customKernelSpecs) { + let customKernelSpecs = sessionContext.kernelPreference + ?.customKernelSpecs as PartialJSONObject; + for (let key in customKernelSpecs) { + let selectedValue = customKernelSpecs[key] as + | PartialJSONValue + | undefined; + + if (kernelParameters.properties) { + let properties = + kernelParameters.properties as PartialJSONObject; + + let kernelParameter = properties[key] as PartialJSONObject; + + if (kernelParameter) { + let kernelParametersTmp = ( + kernelParameters.properties as PartialJSONObject + )[key] as PartialJSONObject; + (kernelParameters.properties as PartialJSONObject)[key] = { + ...kernelParametersTmp, + default: selectedValue + }; + } + } + } + } + + let kernelSpecWidget = new DialogWidget( + kernelParameters, + kernelConfiguration, + formData => { + kernelConfiguration = formData as PartialJSONObject; + select.setAttribute( + 'data-kernel-spec', + JSON.stringify(kernelConfiguration) + ); + }, + trans + ); + + //Update widget + if (kernelSpecsContainer) { + Widget.attach(kernelSpecWidget, kernelSpecsContainer); + } + } + } + } + } + /** * Get the default kernel name given select options. */ diff --git a/packages/console-extension/package.json b/packages/console-extension/package.json index d59899d319b2..7e9f6e6f5f06 100644 --- a/packages/console-extension/package.json +++ b/packages/console-extension/package.json @@ -53,7 +53,8 @@ "@lumino/coreutils": "^2.1.2", "@lumino/disposable": "^2.1.2", "@lumino/properties": "^2.0.1", - "@lumino/widgets": "^2.3.2" + "@lumino/widgets": "^2.3.2", + "@rjsf/utils": "^5.13.4" }, "devDependencies": { "rimraf": "~5.0.5", diff --git a/packages/console-extension/src/index.ts b/packages/console-extension/src/index.ts index cb98a2e93c8b..10dfc7284b60 100644 --- a/packages/console-extension/src/index.ts +++ b/packages/console-extension/src/index.ts @@ -42,6 +42,7 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { ITranslator, nullTranslator } from '@jupyterlab/translation'; import { consoleIcon, + DialogWidget, IFormRendererRegistry, redoIcon, undoIcon @@ -50,6 +51,8 @@ import { find } from '@lumino/algorithm'; import { JSONExt, JSONObject, + PartialJSONObject, + ReadonlyJSONObject, ReadonlyJSONValue, ReadonlyPartialJSONObject, UUID @@ -58,6 +61,8 @@ import { DisposableSet } from '@lumino/disposable'; import { DockLayout, Widget } from '@lumino/widgets'; import foreign from './foreign'; import { cellExecutor } from './cellexecutor'; +import type { RJSFSchema } from '@rjsf/utils'; +import { PageConfig } from '@jupyterlab/coreutils'; /** * The command IDs used by the console plugin. @@ -276,6 +281,57 @@ async function activateConsole( const sessionDialogs = sessionDialogs_ ?? new SessionContextDialogs({ translator }); + const showKernelSpecDialog = async ( + parameters: RJSFSchema, + basePath: string, + args: ReadonlyPartialJSONObject + ) => { + let newArgs = { ...args }; + let kernelConfigurarion: PartialJSONObject = {}; + let label = trans.__('Cancel'); + const buttons = [ + Dialog.cancelButton({ + label + }), + Dialog.okButton({ + label: trans.__('Select'), + ariaLabel: trans.__('Select Kernel') + }) + ]; + + const dialog = new Dialog({ + title: trans.__('Select Kernel'), + body: new DialogWidget( + parameters, + kernelConfigurarion, + formData => { + kernelConfigurarion = formData as PartialJSONObject; + }, + trans + ), + buttons + }); + + const result = await dialog.launch(); + + if (!result.button.accept) { + return; + } + if (result.value) { + if (kernelConfigurarion) { + if (args.kernelPreference) { + let kernelPreference = + args.kernelPreference as ReadonlyPartialJSONObject; + newArgs['kernelPreference'] = { + ...kernelPreference, + customKernelSpecs: kernelConfigurarion + }; + } + } + return createConsole({ basePath, ...newArgs }); + } + }; + // Create a widget tracker for all console panels. const tracker = new WidgetTracker({ namespace: 'console' @@ -320,7 +376,11 @@ async function activateConsole( disposables.add( launcher.add({ command: CommandIDs.create, - args: { isLauncher: true, kernelPreference: { name } }, + args: { + isLauncher: true, + kernelPreference: { name }, + metadata: spec.metadata as ReadonlyJSONObject + }, category: trans.__('Console'), rank, kernelIconUrl, @@ -517,6 +577,9 @@ async function activateConsole( return item.path === path; }); if (model) { + // + //console.log('open console args'); + //console.dir(args); return createConsole(args); } return Promise.reject(`No running kernel session for path: ${path}`); @@ -548,7 +611,25 @@ async function activateConsole( (args['cwd'] as string) || filebrowser?.model.path) ?? ''; - return createConsole({ basePath, ...args }); + // + const metadata = args['metadata'] as ReadonlyJSONObject; + const allowInsecureKernelspecParams = PageConfig.getOption('allow_insecure_kernelspec_params') === 'true' ? true: false; + + if(metadata?.is_secure) { + if (!metadata?.parameters) { + return createConsole({ basePath, ...args }); + } else { + let schema = metadata.parameters as RJSFSchema; + return showKernelSpecDialog(schema, basePath, args); + } + } else { + if (allowInsecureKernelspecParams) { + let schema = metadata.parameters as RJSFSchema; + return showKernelSpecDialog(schema, basePath, args); + } else { + return createConsole({ basePath, ...args }); + } + } } }); diff --git a/packages/console/src/panel.ts b/packages/console/src/panel.ts index ddbb20e69070..959fc42bf428 100644 --- a/packages/console/src/panel.ts +++ b/packages/console/src/panel.ts @@ -88,12 +88,16 @@ export class ConsolePanel extends MainAreaWidget { translator }); this.content.addWidget(this.console); - void sessionContext.initialize().then(async value => { if (value) { - await ( - options.sessionDialogs ?? new SessionContextDialogs({ translator }) - ).selectKernel(sessionContext!); + let dialog = await (options.sessionDialogs ?? + new SessionContextDialogs({ translator })); + if ( + options.kernelPreference && + !options.kernelPreference.customKernelSpecs + ) { + dialog.selectKernel(sessionContext!); + } } this._connected = new Date(); this._updateTitlePanel(); diff --git a/packages/docregistry/src/context.ts b/packages/docregistry/src/context.ts index 177305dff6c0..e517d1993799 100644 --- a/packages/docregistry/src/context.ts +++ b/packages/docregistry/src/context.ts @@ -23,7 +23,11 @@ import { TranslationBundle } from '@jupyterlab/translation'; -import { PartialJSONValue, PromiseDelegate } from '@lumino/coreutils'; +import { + PartialJSONObject, + PartialJSONValue, + PromiseDelegate +} from '@lumino/coreutils'; import { DisposableDelegate, IDisposable } from '@lumino/disposable'; import { ISignal, Signal } from '@lumino/signaling'; import { Widget } from '@lumino/widgets'; @@ -529,7 +533,9 @@ export class Context< /** * Handle an initial population. */ - private async _populate(): Promise { + private async _populate( + customKernelSpecs?: undefined | PartialJSONObject + ): Promise { this._isPopulated = true; this._isReady = true; this._populatedPromise.resolve(void 0); @@ -539,6 +545,15 @@ export class Context< if (this.isDisposed) { return; } + + if ( + (!customKernelSpecs || Object.keys(customKernelSpecs).length === 0) && + this.sessionContext.kernelPreference.customKernelSpecs + ) { + customKernelSpecs = + this.sessionContext.kernelPreference.customKernelSpecs; + } + // Update the kernel preference. const name = this._model.defaultKernelName || @@ -546,7 +561,8 @@ export class Context< this.sessionContext.kernelPreference = { ...this.sessionContext.kernelPreference, name, - language: this._model.defaultKernelLanguage + language: this._model.defaultKernelLanguage, + customKernelSpecs: customKernelSpecs }; // Note: we don't wait on the session to initialize // so that the user can be shown the content before diff --git a/packages/docregistry/src/registry.ts b/packages/docregistry/src/registry.ts index ca9049af5a5a..14cf9ef8c943 100644 --- a/packages/docregistry/src/registry.ts +++ b/packages/docregistry/src/registry.ts @@ -643,6 +643,7 @@ export class DocumentRegistry implements IDisposable { const language = modelFactory.preferredLanguage(PathExt.basename(path)); const name = kernel && kernel.name; const id = kernel && kernel.id; + const customKernelSpecs = kernel && kernel.custom_kernel_specs; return { id, name, @@ -650,7 +651,8 @@ export class DocumentRegistry implements IDisposable { shouldStart: widgetFactory.preferKernel, canStart: widgetFactory.canStartKernel, shutdownOnDispose: widgetFactory.shutdownOnClose, - autoStartDefault: widgetFactory.autoStartDefault + autoStartDefault: widgetFactory.autoStartDefault, + customKernelSpecs: customKernelSpecs ? customKernelSpecs : undefined }; } diff --git a/packages/notebook-extension/package.json b/packages/notebook-extension/package.json index 1b005ccfdf9f..9102b67612cd 100644 --- a/packages/notebook-extension/package.json +++ b/packages/notebook-extension/package.json @@ -73,7 +73,9 @@ "@lumino/messaging": "^2.0.1", "@lumino/polling": "^2.1.2", "@lumino/widgets": "^2.3.2", + "@rjsf/core": "^5.13.4", "@rjsf/utils": "^5.13.4", + "@rjsf/validator-ajv8": "^5.13.4", "react": "^18.2.0" }, "devDependencies": { diff --git a/packages/notebook-extension/src/customKernelWidget.tsx b/packages/notebook-extension/src/customKernelWidget.tsx new file mode 100644 index 000000000000..884b6a1a79d1 --- /dev/null +++ b/packages/notebook-extension/src/customKernelWidget.tsx @@ -0,0 +1,93 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +import { ReactWidget } from '@jupyterlab/ui-components'; +import React from 'react'; + +import Form, { IChangeEvent } from '@rjsf/core'; +import { RJSFSchema, UiSchema } from '@rjsf/utils'; +import validator from '@rjsf/validator-ajv8'; +import { PartialJSONObject } from '@lumino/coreutils'; + +type FormDataProps = + | IChangeEvent + | undefined + | PartialJSONObject + | {}; + +/** + * Form to select custom properties of kernel + * + * @returns The React component + */ + +const FormComponent = (props: { + schema: RJSFSchema; + kernelConfigurarion: FormDataProps; + updateFormData: (formData: FormDataProps) => void; +}): JSX.Element => { + const uiSchema: UiSchema = { + 'ui:options': { + submitButtonOptions: { + props: { + disabled: true + }, + norender: true + } + } + }; + + const onChange = ({ formData }: IChangeEvent) => { + console.log('+++'); + props.updateFormData(formData); + }; + + return ( +
+ ); +}; + +/** + * A Button Lumino Widget that wraps a FormComponent. + */ +export class DialogWidget extends ReactWidget { + schema: RJSFSchema; + formData: FormDataProps | {}; + updateFormData: (formData: FormDataProps) => void; + kernelConfigurarion: FormDataProps; + /** + * Constructs a new FormWidget. + */ + constructor( + schema: RJSFSchema, + kernelConfigurarion: FormDataProps, + updateFormData: (formData: FormDataProps) => void + ) { + super(); + this.schema = schema; + this.formData = undefined; + this.kernelConfigurarion = kernelConfigurarion; + this.updateFormData = updateFormData; + } + + getValue(): FormDataProps | {} { + return this.kernelConfigurarion; + } + + render(): JSX.Element { + return ( + + ); + } +} diff --git a/packages/notebook-extension/src/index.ts b/packages/notebook-extension/src/index.ts index ed24f02c9335..d2ad716a63da 100644 --- a/packages/notebook-extension/src/index.ts +++ b/packages/notebook-extension/src/index.ts @@ -5,7 +5,7 @@ * @module notebook-extension */ -import type { FieldProps } from '@rjsf/utils'; +import type { FieldProps, RJSFSchema } from '@rjsf/utils'; import { ILabShell, @@ -103,6 +103,7 @@ import { buildIcon, copyIcon, cutIcon, + DialogWidget, duplicateIcon, fastForwardIcon, IFormRenderer, @@ -121,6 +122,8 @@ import { CommandRegistry } from '@lumino/commands'; import { JSONExt, JSONObject, + PartialJSONObject, + ReadonlyJSONObject, ReadonlyJSONValue, ReadonlyPartialJSONObject, UUID @@ -135,6 +138,7 @@ import { CellMetadataField, NotebookMetadataField } from './tool-widgets/metadataEditorFields'; +//import { CustomKernelSpecForm } from '@jupyterlab/services/src/kernelspec/kernelspec'; /** * The command IDs used by the notebook plugin. @@ -1844,6 +1848,54 @@ function activateNotebookHandler( } } + const showKernelSpecDialog = async ( + parameters: RJSFSchema, + cwd: string, + kernelId: string, + kernelName: string + ) => { + let kernelConfigurarion: PartialJSONObject = {}; + let label = trans.__('Cancel'); + const buttons = [ + Dialog.cancelButton({ + label + }), + Dialog.okButton({ + label: trans.__('Select'), + ariaLabel: trans.__('Select Kernel') + }) + ]; + + // const autoStartDefault = sessionContext.kernelPreference.autoStartDefault; + + const dialog = new Dialog({ + title: trans.__('Select Kernel'), + body: new DialogWidget( + parameters, + kernelConfigurarion, + formData => { + kernelConfigurarion = formData as PartialJSONObject; + }, + trans + ), + buttons + }); + + const result = await dialog.launch(); + + if (!result.button.accept) { + return; + } + if (result.value) { + let customKernelSpecs = undefined; + if (kernelConfigurarion) { + customKernelSpecs = kernelConfigurarion; + } + + createNew(cwd, kernelId, kernelName, customKernelSpecs); + } + }; + /** * Update the setting values. */ @@ -1938,7 +1990,8 @@ function activateNotebookHandler( const createNew = async ( cwd: string, kernelId: string, - kernelName: string + kernelName: string, + customKernelSpecs?: undefined | PartialJSONObject ) => { const model = await commands.execute('docmanager:new-untitled', { path: cwd, @@ -1948,7 +2001,11 @@ function activateNotebookHandler( const widget = (await commands.execute('docmanager:open', { path: model.path, factory: FACTORY, - kernel: { id: kernelId, name: kernelName } + kernel: { + id: kernelId, + name: kernelName, + custom_kernel_specs: customKernelSpecs + } })) as unknown as IDocumentWidget; widget.isUntitled = true; return widget; @@ -1960,6 +2017,7 @@ function activateNotebookHandler( label: args => { const kernelName = (args['kernelName'] as string) || ''; if (args['isLauncher'] && args['kernelName'] && services.kernelspecs) { + // return ( services.kernelspecs.specs?.kernelspecs[kernelName]?.display_name ?? '' @@ -1975,10 +2033,27 @@ function activateNotebookHandler( execute: args => { const currentBrowser = filebrowserFactory?.tracker.currentWidget ?? defaultBrowser; + //if has enum then calll const cwd = (args['cwd'] as string) || (currentBrowser?.model.path ?? ''); const kernelId = (args['kernelId'] as string) || ''; const kernelName = (args['kernelName'] as string) || ''; - return createNew(cwd, kernelId, kernelName); + const metadata = args['metadata'] as ReadonlyJSONObject; + const allowInsecureKernelspecParams = PageConfig.getOption('allow_insecure_kernelspec_params') === 'true' ? true: false; + if(metadata?.is_secure) { + if (!metadata?.parameters) { + return createNew(cwd, kernelId, kernelName); + } else { + let schema = metadata.parameters as RJSFSchema; + showKernelSpecDialog(schema, cwd, kernelId, kernelName); + } + } else { + if (allowInsecureKernelspecParams) { + let schema = metadata.parameters as RJSFSchema; + showKernelSpecDialog(schema, cwd, kernelId, kernelName); + } else { + return createNew(cwd, kernelId, kernelName); + } + } } }); @@ -2000,12 +2075,20 @@ function activateNotebookHandler( for (const name in specs.kernelspecs) { const rank = name === specs.default ? 0 : Infinity; const spec = specs.kernelspecs[name]!; + console.log('spec'); + console.dir(spec); const kernelIconUrl = spec.resources['logo-svg'] || spec.resources['logo-64x64']; + //if has enum then add one icon + disposables.add( launcher.add({ command: CommandIDs.createNew, - args: { isLauncher: true, kernelName: name }, + args: { + isLauncher: true, + kernelName: name, + metadata: spec.metadata as ReadonlyJSONObject + }, category: trans.__('Notebook'), rank, kernelIconUrl, diff --git a/packages/notebook/src/panel.ts b/packages/notebook/src/panel.ts index 9e8f08766ddd..d437613fdfaf 100644 --- a/packages/notebook/src/panel.ts +++ b/packages/notebook/src/panel.ts @@ -11,8 +11,8 @@ import { isMarkdownCellModel } from '@jupyterlab/cells'; import { PageConfig } from '@jupyterlab/coreutils'; import { DocumentRegistry, DocumentWidget } from '@jupyterlab/docregistry'; import { Kernel, KernelMessage, Session } from '@jupyterlab/services'; -import { ITranslator } from '@jupyterlab/translation'; import { Token } from '@lumino/coreutils'; +import { ITranslator } from '@jupyterlab/translation'; import { INotebookModel } from './model'; import { Notebook, StaticNotebook } from './widget'; import { Message } from '@lumino/messaging'; @@ -253,10 +253,11 @@ export class NotebookPanel extends DocumentWidget { if (this.isDisposed) { return; } + this.model!.setMetadata('kernelspec', { name: kernel.name, display_name: spec?.display_name, - language: spec?.language + language: spec?.language, }); } diff --git a/packages/services/src/kernel/restapi.ts b/packages/services/src/kernel/restapi.ts index f2399cc1f3d8..e84410c8a17a 100644 --- a/packages/services/src/kernel/restapi.ts +++ b/packages/services/src/kernel/restapi.ts @@ -4,6 +4,7 @@ import { ServerConnection } from '../serverconnection'; import { URLExt } from '@jupyterlab/coreutils'; import { validateModel, validateModels } from './validate'; +import { PartialJSONObject } from '@lumino/coreutils'; /** * The kernel model provided by the server. @@ -47,6 +48,11 @@ export interface IModel { * The traceback for a dead kernel, if applicable. */ readonly traceback?: string; + + /** + * Custom kernel specifications for running a kernel + */ + custom_kernel_specs?: undefined | PartialJSONObject; } /** @@ -101,6 +107,7 @@ export async function startNew( method: 'POST', body: JSON.stringify(options) }; + // const response = await ServerConnection.makeRequest(url, init, settings); if (response.status !== 201) { const err = await ServerConnection.ResponseError.create(response); diff --git a/packages/services/src/session/restapi.ts b/packages/services/src/session/restapi.ts index 6198c2829620..5b73df20921c 100644 --- a/packages/services/src/session/restapi.ts +++ b/packages/services/src/session/restapi.ts @@ -109,6 +109,7 @@ export async function startSession( method: 'POST', body: JSON.stringify(options) }; + const response = await ServerConnection.makeRequest(url, init, settings); if (response.status !== 201) { const err = await ServerConnection.ResponseError.create(response); @@ -128,6 +129,7 @@ export async function updateSession( settings: ServerConnection.ISettings = ServerConnection.makeSettings() ): Promise { const url = getSessionUrl(settings.baseUrl, model.id); + const init = { method: 'PATCH', body: JSON.stringify(model) diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index cb5473c0b8eb..261021c53503 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -58,6 +58,7 @@ "@lumino/widgets": "^2.3.2", "@rjsf/core": "^5.13.4", "@rjsf/utils": "^5.13.4", + "@rjsf/validator-ajv8": "^5.13.4", "react": "^18.2.0", "react-dom": "^18.2.0", "typestyle": "^2.0.4" diff --git a/packages/ui-components/src/components/customform.tsx b/packages/ui-components/src/components/customform.tsx new file mode 100644 index 000000000000..c35df8d16e58 --- /dev/null +++ b/packages/ui-components/src/components/customform.tsx @@ -0,0 +1,111 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +import React, { createRef, RefObject, useState } from 'react'; +import Form, { IChangeEvent } from '@rjsf/core'; +import { RJSFSchema, UiSchema } from '@rjsf/utils'; +import validator from '@rjsf/validator-ajv8'; +import { PartialJSONObject } from '@lumino/coreutils'; +import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; +import { ReactWidget } from './vdom'; +import { FormComponent } from './form'; + +type FormDataProps = + | IChangeEvent + | undefined + | PartialJSONObject; + +/** + * Form to select custom properties of kernel + * + * @returns The React component + */ + +const FormComponentWrapper = (props: { + schema: RJSFSchema; + kernelConfigurarion: FormDataProps; + updateFormData: (formData: FormDataProps) => void; +}): JSX.Element => { + const [isUpdate, setUpdate] = useState(0); + + const formRef = createRef() as RefObject>; + + const uiSchema: UiSchema = { + 'ui:options': { + submitButtonOptions: { + props: { + disabled: true + }, + norender: true + } + } + }; + + const onChange = ({ formData }: IChangeEvent) => { + if (!isUpdate) { + props.updateFormData(formData); + } + if (formRef.current && formRef?.current.validateForm()) { + props.updateFormData(formData); + } + let update = isUpdate + 1; + setUpdate(update); + // } + }; + + const formData: Record = {}; + + return ( + + ); +}; + +/** + * A Dialog Widget that wraps a FormComponent. + */ +export class DialogWidget extends ReactWidget { + schema: RJSFSchema; + formData: FormDataProps; + updateFormData: (formData: FormDataProps) => void; + kernelConfigurarion: FormDataProps; + /** + * Constructs a new FormWidget. + */ + constructor( + schema: RJSFSchema, + kernelConfigurarion: FormDataProps, + updateFormData: (formData: FormDataProps) => void, + trans: IRenderMime.TranslationBundle + ) { + super(); + this.schema = schema; + this.formData = undefined; + this.kernelConfigurarion = kernelConfigurarion; + this.updateFormData = updateFormData; + } + + getValue(): FormDataProps { + return this.kernelConfigurarion; + } + + render(): JSX.Element { + return ( + + ); + } +} diff --git a/packages/ui-components/src/components/form.tsx b/packages/ui-components/src/components/form.tsx index 997c207eb2d9..0b1aec6b292b 100644 --- a/packages/ui-components/src/components/form.tsx +++ b/packages/ui-components/src/components/form.tsx @@ -16,10 +16,11 @@ import { getTemplate, ObjectFieldTemplateProps, Registry, + RJSFSchema, UiSchema } from '@rjsf/utils'; -import React from 'react'; +import React, { forwardRef } from 'react'; import { addIcon, caretDownIcon, @@ -617,7 +618,10 @@ export interface IFormComponentProps /** * Generic rjsf form component for JupyterLab UI. */ -export function FormComponent(props: IFormComponentProps): JSX.Element { +export const FormComponent = forwardRef(function FormComponent( + props: IFormComponentProps, + ref: React.RefObject> | null +): JSX.Element { const { buttonStyle, compact, @@ -677,6 +681,11 @@ export function FormComponent(props: IFormComponentProps): JSX.Element { }; return ( - + ); -} +}); diff --git a/packages/ui-components/src/components/index.ts b/packages/ui-components/src/components/index.ts index b4b3cd986b26..9da0a0336a1e 100644 --- a/packages/ui-components/src/components/index.ts +++ b/packages/ui-components/src/components/index.ts @@ -19,3 +19,4 @@ export * from './table'; export * from './toolbar'; export * from './vdom'; export * from './windowedlist'; +export * from './customform';