Skip to content

Commit 95511e2

Browse files
authored
Cell factory (#8)
* Adds cells factory to handle SQL cells with mimetype * Adds some doc string * Add class only for SQL cell * Adds test * Remove leftover trials
1 parent eb82ddf commit 95511e2

File tree

6 files changed

+200
-12
lines changed

6 files changed

+200
-12
lines changed

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"@jupyterlab/apputils": "^4.0.0",
6262
"@jupyterlab/cell-toolbar": "^4.0.0",
6363
"@jupyterlab/cells": "^4.0.0",
64+
"@jupyterlab/codeeditor": "^4.0.0",
6465
"@jupyterlab/coreutils": "^6.0.0",
6566
"@jupyterlab/filebrowser": "^4.0.0",
6667
"@jupyterlab/notebook": "^4.0.0",
@@ -121,7 +122,10 @@
121122
},
122123
"extension": true,
123124
"outputDir": "jupyter_sql_cell/labextension",
124-
"schemaDir": "schema"
125+
"schemaDir": "schema",
126+
"disabledExtensions": [
127+
"@jupyterlab/notebook-extension:factory"
128+
]
125129
},
126130
"eslintIgnore": [
127131
"node_modules",

src/cellfactory.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { CellModel, RawCell } from '@jupyterlab/cells';
2+
import { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
3+
import { Notebook, NotebookPanel } from '@jupyterlab/notebook';
4+
5+
import { SQL_MIMETYPE } from './common';
6+
import { IMapChange } from '@jupyter/ydoc';
7+
8+
/**
9+
* The class of the custom raw cell.
10+
*/
11+
const SQL_CELL_CLASS = 'jp-SqlCell';
12+
13+
/**
14+
* The cell content factory.
15+
*/
16+
export class CustomContentFactory
17+
extends Notebook.ContentFactory
18+
implements NotebookPanel.IContentFactory
19+
{
20+
/**
21+
* Create a new content area for the panel.
22+
*/
23+
createNotebook(options: Notebook.IOptions): Notebook {
24+
return new Notebook(options);
25+
}
26+
27+
/**
28+
* Create raw/SQL cell.
29+
*/
30+
createRawCell(options: RawCell.IOptions): RawCell {
31+
return new RawSqlCell(options).initializeState();
32+
}
33+
}
34+
35+
/**
36+
* The raw/SQL cell.
37+
*/
38+
export class RawSqlCell extends RawCell {
39+
/**
40+
* The constructor method.
41+
*/
42+
constructor(options: RawCell.IOptions) {
43+
super(options);
44+
if (this.model.getMetadata('format') === SQL_MIMETYPE) {
45+
this._sqlCell();
46+
}
47+
}
48+
49+
/**
50+
* Handle changes in the metadata.
51+
*/
52+
protected onMetadataChanged(model: CellModel, args: IMapChange<any>): void {
53+
super.onMetadataChanged(model, args);
54+
if (args.key === 'format') {
55+
if (this.model.getMetadata('format') === SQL_MIMETYPE) {
56+
this._sqlCell();
57+
} else {
58+
this._rawCell();
59+
}
60+
}
61+
}
62+
63+
/**
64+
* Switch to raw cell type.
65+
*/
66+
private _rawCell() {
67+
this.removeClass(SQL_CELL_CLASS);
68+
const trans = this.translator.load('jupyterlab');
69+
this.node.setAttribute('aria-label', trans.__('Raw Cell Content'));
70+
this.model.mimeType = IEditorMimeTypeService.defaultMimeType;
71+
}
72+
73+
/**
74+
* Switch to SQL cell type, using codemirror SQL mimetype.
75+
*/
76+
private _sqlCell() {
77+
this.addClass(SQL_CELL_CLASS);
78+
const trans = this.translator.load('jupyterlab');
79+
this.node.setAttribute('aria-label', trans.__('SQL Cell Content'));
80+
this.model.mimeType = SQL_MIMETYPE;
81+
}
82+
}

src/common.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,49 @@
11
import { ICellModel } from '@jupyterlab/cells';
22

3+
/**
4+
* The command IDs of the extensions.
5+
*/
36
export namespace CommandIDs {
7+
/**
8+
* Switch cell type between rax and SQL.
9+
*/
410
export const switchSQL = 'jupyter-sql-cell:switch';
11+
/**
12+
* Execute the query on the server.
13+
*/
514
export const run = 'jupyter-sql-cell:execute';
615
}
716

8-
export const METADATA_SQL_FORMAT = 'application/sql';
17+
/**
18+
* The SQL mimetype used by codemirror.
19+
*/
20+
export const SQL_MIMETYPE = 'text/x-sql';
921

22+
/**
23+
* The SqlCell namespace.
24+
*/
1025
export namespace SqlCell {
26+
/**
27+
* Whether the cell is a raw cell.
28+
*
29+
* @param model - the cell model.
30+
*/
1131
export function isRaw(model: ICellModel | undefined) {
1232
if (!model) {
1333
return false;
1434
}
1535
return model.type === 'raw';
1636
}
37+
38+
/**
39+
* Whether the cell is an SQL cell.
40+
*
41+
* @param model - the cell model.
42+
*/
1743
export function isSqlCell(model: ICellModel | undefined) {
1844
if (!model) {
1945
return false;
2046
}
21-
return (
22-
model.type === 'raw' &&
23-
model.getMetadata('format') === METADATA_SQL_FORMAT
24-
);
47+
return model.type === 'raw' && model.getMetadata('format') === SQL_MIMETYPE;
2548
}
2649
}

src/index.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ import {
44
JupyterFrontEndPlugin
55
} from '@jupyterlab/application';
66
import { ICommandPalette, IToolbarWidgetRegistry } from '@jupyterlab/apputils';
7+
import { IEditorServices } from '@jupyterlab/codeeditor';
78
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
89
import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
910
import { Contents, ContentsManager } from '@jupyterlab/services';
1011
import { ISettingRegistry } from '@jupyterlab/settingregistry';
1112
import { runIcon } from '@jupyterlab/ui-components';
1213

14+
import { CustomContentFactory } from './cellfactory';
1315
import { requestAPI } from './handler';
14-
import { CommandIDs, METADATA_SQL_FORMAT, SqlCell } from './common';
16+
import { CommandIDs, SQL_MIMETYPE, SqlCell } from './common';
1517
import { SqlWidget } from './widget';
1618

1719
/**
@@ -79,11 +81,12 @@ const plugin: JupyterFrontEndPlugin<void> = {
7981
if (!model || model.type !== 'raw') {
8082
return;
8183
}
82-
if (model.getMetadata('format') !== METADATA_SQL_FORMAT) {
83-
model.setMetadata('format', METADATA_SQL_FORMAT);
84-
} else if (model.getMetadata('format') === METADATA_SQL_FORMAT) {
84+
if (model.getMetadata('format') !== SQL_MIMETYPE) {
85+
model.setMetadata('format', SQL_MIMETYPE);
86+
} else if (model.getMetadata('format') === SQL_MIMETYPE) {
8587
model.deleteMetadata('format');
8688
}
89+
8790
app.commands.notifyCommandChanged(CommandIDs.switchSQL);
8891
app.commands.notifyCommandChanged(CommandIDs.run);
8992
},
@@ -100,6 +103,21 @@ const plugin: JupyterFrontEndPlugin<void> = {
100103
}
101104
};
102105

106+
/**
107+
* The notebook cell factory provider, to handle SQL cells.
108+
*/
109+
const cellFactory: JupyterFrontEndPlugin<NotebookPanel.IContentFactory> = {
110+
id: '@jupyter/sql-cell:content-factory',
111+
description: 'Provides the notebook cell factory.',
112+
provides: NotebookPanel.IContentFactory,
113+
requires: [IEditorServices],
114+
autoStart: true,
115+
activate: (app: JupyterFrontEnd, editorServices: IEditorServices) => {
116+
const editorFactory = editorServices.factoryService.newInlineEditor;
117+
return new CustomContentFactory({ editorFactory });
118+
}
119+
};
120+
103121
/**
104122
* The notebook toolbar widget.
105123
*/
@@ -143,7 +161,7 @@ const notebookToolbarWidget: JupyterFrontEndPlugin<void> = {
143161
}
144162
};
145163

146-
export default [notebookToolbarWidget, plugin];
164+
export default [cellFactory, notebookToolbarWidget, plugin];
147165

148166
namespace Private {
149167
/**

ui-tests/tests/jupyter_sql_cell.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,63 @@ test.describe('cell toolbar', () => {
6969
).toBeEnabled();
7070
});
7171
});
72+
73+
test.describe('cell factory', () => {
74+
test.beforeEach(async ({ page, request, tmpPath }) => {
75+
const contents = galata.newContentsHelper(request);
76+
await contents.uploadFile(
77+
path.resolve(__dirname, `./notebooks/${fileName}`),
78+
`${tmpPath}/${fileName}`
79+
);
80+
await page.notebook.openByPath(`${tmpPath}/${fileName}`);
81+
await page.notebook.activate(fileName);
82+
});
83+
84+
test.afterEach(async ({ request, tmpPath }) => {
85+
const contents = galata.newContentsHelper(request);
86+
await contents.deleteDirectory(tmpPath);
87+
});
88+
89+
test('SQL cells only should have jp-SqlCell class', async ({ page }) => {
90+
const cells = page.locator('.jp-Notebook .jp-Cell');
91+
92+
await expect(cells).toHaveCount(3);
93+
94+
await expect(cells.nth(0)).not.toHaveClass(/jp-SqlCell/);
95+
await expect(cells.nth(1)).not.toHaveClass(/jp-SqlCell/);
96+
await expect(cells.nth(2)).not.toHaveClass(/jp-SqlCell/);
97+
98+
await (await page.notebook.getCellInput(2))?.click();
99+
await page
100+
.locator('.jp-cell-toolbar [data-command="jupyter-sql-cell:switch"]')
101+
.click();
102+
103+
await expect(cells.nth(2)).toHaveClass(/jp-SqlCell/);
104+
});
105+
106+
test('SQL cells should have SQL mimetype', async ({ page }) => {
107+
const cells = page.locator('.jp-Notebook .jp-Cell');
108+
109+
// SQl cells should have SQL language.
110+
await (await page.notebook.getCellInput(2))?.click();
111+
await page
112+
.locator('.jp-cell-toolbar [data-command="jupyter-sql-cell:switch"]')
113+
.click();
114+
115+
await expect(
116+
cells.nth(2).locator('div[contenteditable="true"]')
117+
).toHaveAttribute('data-language', 'sql');
118+
119+
// Raw cells not have codemirror language.
120+
await page
121+
.locator('.jp-cell-toolbar [data-command="jupyter-sql-cell:switch"]')
122+
.click();
123+
124+
expect(
125+
await cells
126+
.nth(2)
127+
.locator('div[contenteditable="true"]')
128+
.getAttribute('data-language')
129+
).toBe(null);
130+
});
131+
});

yarn.lock

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,7 @@ __metadata:
20672067
"@jupyterlab/builder": ^4.0.0
20682068
"@jupyterlab/cell-toolbar": ^4.0.0
20692069
"@jupyterlab/cells": ^4.0.0
2070+
"@jupyterlab/codeeditor": ^4.0.0
20702071
"@jupyterlab/coreutils": ^6.0.0
20712072
"@jupyterlab/filebrowser": ^4.0.0
20722073
"@jupyterlab/notebook": ^4.0.0
@@ -2287,7 +2288,7 @@ __metadata:
22872288
languageName: node
22882289
linkType: hard
22892290

2290-
"@jupyterlab/codeeditor@npm:^4.0.6":
2291+
"@jupyterlab/codeeditor@npm:^4.0.0, @jupyterlab/codeeditor@npm:^4.0.6":
22912292
version: 4.0.6
22922293
resolution: "@jupyterlab/codeeditor@npm:4.0.6"
22932294
dependencies:

0 commit comments

Comments
 (0)