Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database select update #20

Merged
merged 4 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 55 additions & 18 deletions src/cellfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { ReactiveToolbar } from '@jupyterlab/ui-components';
import { Message } from '@lumino/messaging';
import { PanelLayout, SingletonLayout, Widget } from '@lumino/widgets';

import { ICustomCodeCell, MAGIC } from './common';
import { ICustomCodeCell, MAGIC, MagicLine } from './common';
import { IKernelInjection } from './kernelInjection';
import { IDatabasesPanel } from './sidepanel';
import { DatabaseSelect, VariableName } from './widgets';
import { ISignal, Signal } from '@lumino/signaling';

/**
* The class of the header.
Expand Down Expand Up @@ -50,7 +51,8 @@ export class NotebookContentFactory
const cell = new CustomCodeCell({
...options,
contentFactory: cellContentFactory,
kernelInjection
kernelInjection,
databasesPanel
}).initializeState();
return cell;
}
Expand Down Expand Up @@ -81,24 +83,14 @@ class CustomCodeCell extends CodeCell implements ICustomCodeCell {
constructor(options: CustomCodeCell.IOptions) {
super(options);
this._kernelInjection = options.kernelInjection;

this._databasePanel = options.databasesPanel;
this.model.sharedModel.changed.connect(this._onSharedModelChanged, this);

this._kernelInjection.statusChanged.connect(() => {
this._checkSource();
}, this);
}

protected initializeDOM(): void {
super.initializeDOM();
this._header = (this.layout as PanelLayout).widgets.find(
widget => widget instanceof CellHeader
) as CellHeader;

this._header.createToolbar(this);
this._checkSource();
}

/**
* Getter and setter of the SQL status.
*/
Expand All @@ -120,6 +112,23 @@ class CustomCodeCell extends CodeCell implements ICustomCodeCell {
this._variable = name;
}

/**
* A signal emitted when the first line changed.
*/
get databaseChanged(): ISignal<ICustomCodeCell, string> {
return this._databaseChanged;
}

protected initializeDOM(): void {
super.initializeDOM();
this._header = (this.layout as PanelLayout).widgets.find(
widget => widget instanceof CellHeader
) as CellHeader;

this._header.createToolbar(this);
this._checkSource();
}

protected onStateChanged(
model: ICellModel,
args: IChangedArgs<any, any, string>
Expand All @@ -139,32 +148,55 @@ class CustomCodeCell extends CodeCell implements ICustomCodeCell {
* Check the source of the cell for the MAGIC command, and attach or detach
* the toolbar if necessary.
*/
private _checkSource() {
private _checkSource(): boolean {
if (!this._kernelInjection.getStatus(this)) {
this.isSQL = false;
return;
return false;
}
const sourceStart = this.model.sharedModel.source.substring(0, 5);
const sourceStart = this.model.sharedModel.source.substring(
0,
MAGIC.length
);
if (sourceStart === MAGIC && !this.isSQL) {
this.isSQL = true;
} else if (sourceStart !== MAGIC && this.isSQL) {
this.isSQL = false;
}
return this.isSQL;
}

/**
* Triggered when the shared model change.
*/
private _onSharedModelChanged = (_: ISharedCodeCell, change: CellChange) => {
if (this._kernelInjection.getStatus(this) && change.sourceChange) {
this._checkSource();
const firstLine = this.model.sharedModel.source.split('\n')[0];

// If an object with the key 'retain' exists, it will give the position of the
// change. Otherwise we assume the change occurs at position 0;
const position =
change.sourceChange.find(change => change.retain !== undefined)
?.retain || 0;

// Check if the change occurs on the first line to update header and widgets.
if (position <= firstLine.length) {
if (this._checkSource()) {
const databaseURL = MagicLine.getDatabaseUrl(this.model);
const databaseAlias =
this._databasePanel.databases.find(db => db.url === databaseURL)
?.alias ?? ' - ';
this._databaseChanged.emit(databaseAlias);
}
}
}
};

private _header: CellHeader | undefined = undefined;
private _kernelInjection: IKernelInjection;
private _databasePanel: IDatabasesPanel;
private _variable: string | null = null;
private _isSQL = false;
private _databaseChanged = new Signal<ICustomCodeCell, string>(this);
}

/**
Expand All @@ -179,6 +211,10 @@ namespace CustomCodeCell {
* The kernel injection, whether the kernel can handle sql magics or not.
*/
kernelInjection: IKernelInjection;
/**
* The databases panel, containing the known databases.
*/
databasesPanel: IDatabasesPanel;
}
}

Expand Down Expand Up @@ -245,7 +281,8 @@ export class CellHeader extends Widget implements ICellHeader {
createToolbar(cell: CustomCodeCell) {
const databaseSelect = new DatabaseSelect({
cellModel: cell?.model,
databasesPanel: this._databasesPanel
databasesPanel: this._databasesPanel,
databaseChanged: cell.databaseChanged
});

this._toolbar.addItem('select', databaseSelect);
Expand Down
46 changes: 46 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { ICellModel } from '@jupyterlab/cells';
import { Database } from './databases';

/**
* The code to inject to the kernel to load the sql magic.
*/
Expand All @@ -21,3 +24,46 @@ export interface ICustomCodeCell {
*/
variable: string | null;
}

/**
* The MagicLine namespace.
*/
export namespace MagicLine {
/**
* Return the database URL from the magic line of a cell.
*
* @param cellModel - the model of the cell to look for the database URL.
*/
export function getDatabaseUrl(
cellModel: ICellModel | undefined
): string | undefined {
if (!cellModel) {
return;
}
const magicLine = cellModel.sharedModel.source.split('\n')[0];
const regexp = new RegExp(`^${MAGIC}\\s+([^\\s]+)`);
const match = magicLine.match(regexp);
if (match && match.length > 1) {
return match[1];
}
}

/**
* Update the contents of the magic line of the cell, accordingly to the selection.
*
* @param cellModel - the model of the cell whose contents are to be modified.
* @param database - the selected database.
*/
export function setDatabaseUrl(
cellModel: ICellModel,
database: Database | undefined
): void {
const sourceArray = cellModel.sharedModel.source.split('\n');
const magicLine = sourceArray[0].split(/\s+/);
if (database) {
magicLine[1] = `${database.url}`;
}
sourceArray[0] = magicLine.join(' ');
cellModel.sharedModel.source = sourceArray.join('\n');
}
}
83 changes: 26 additions & 57 deletions src/widgets.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ICellModel } from '@jupyterlab/cells';
import { ReactWidget } from '@jupyterlab/ui-components';
import { ReactWidget, UseSignal } from '@jupyterlab/ui-components';
import { ISignal } from '@lumino/signaling';
import React from 'react';

import { ICustomCodeCell, MAGIC } from './common';
import { Database } from './databases';
import { ICustomCodeCell, MagicLine } from './common';
import { IDatabasesPanel } from './sidepanel';

/**
Expand All @@ -13,24 +13,26 @@ export class DatabaseSelect extends ReactWidget {
constructor(options: {
cellModel: ICellModel | undefined;
databasesPanel: IDatabasesPanel;
databaseChanged: ISignal<ICustomCodeCell, string>;
}) {
super();
this._cellModel = options.cellModel;
this._databasesPanel = options.databasesPanel;
this._databaseChanged = options.databaseChanged;
}

onChange = (event: React.FormEvent) => {
const selection = (event.target as HTMLSelectElement).value;
const database = this._databasesPanel.get_database(selection);
if (this._cellModel && database) {
Private.setDatabaseUrl(this._cellModel, database);
MagicLine.setDatabaseUrl(this._cellModel, database);
}
};

render(): JSX.Element {
const defaultValue = ' - ';
let currentDatabase = defaultValue;
const url = Private.getDatabaseUrl(this._cellModel);
const url = MagicLine.getDatabaseUrl(this._cellModel);
const aliases: string[] = [];
this._databasesPanel?.databases.forEach(database => {
aliases.push(database.alias);
Expand All @@ -41,27 +43,33 @@ export class DatabaseSelect extends ReactWidget {
return (
<label>
Database:&nbsp;
<select
onChange={this.onChange}
className={'jp-sqlcell-select'}
disabled={this._cellModel?.type !== 'code'}
>
<option disabled selected={currentDatabase === defaultValue}>
{defaultValue}
</option>
;
{aliases.map(alias => {
<UseSignal signal={this._databaseChanged} initialArgs={currentDatabase}>
{(_, databaseURL) => {
return (
<option selected={currentDatabase === alias}>{alias}</option>
<select
onChange={this.onChange}
className={'jp-sqlcell-select'}
disabled={this._cellModel?.type !== 'code'}
>
<option disabled selected={databaseURL === defaultValue}>
{defaultValue}
</option>
{aliases.map(alias => {
return (
<option selected={databaseURL === alias}>{alias}</option>
);
})}
</select>
);
})}
</select>
}}
</UseSignal>
</label>
);
}

private _cellModel: ICellModel | undefined;
private _databasesPanel: IDatabasesPanel;
private _databaseChanged: ISignal<ICustomCodeCell, string>;
}

/**
Expand Down Expand Up @@ -96,42 +104,3 @@ export class VariableName extends ReactWidget {
private _cell: ICustomCodeCell;
private _value: string;
}

/**
* The private namespace.
*/
namespace Private {
export function getDatabaseUrl(
cellModel: ICellModel | undefined
): string | undefined {
if (!cellModel) {
return;
}
const regexp = new RegExp(`^${MAGIC}\\s+((?!\\s).*)`);
const magicLine = cellModel.sharedModel.source.split('\n')[0];
const match = magicLine.match(regexp);
if (match && match.length > 1) {
return match[1];
}
}

/**
* Update the contents of the magic line of the cell, accordingly to the selection.
*
* @param cellModel - the model of the cell whose contents are to be modified.
* @param database - the selected database.
*/
export function setDatabaseUrl(
cellModel: ICellModel,
database: Database | undefined
): void {
let magicLine = MAGIC;
if (database) {
magicLine += ` ${database.url}`;
}
const source = cellModel.sharedModel.source;
const sourceArray = source.split('\n');
sourceArray[0] = magicLine;
cellModel.sharedModel.source = sourceArray.join('\n');
}
}
Loading