Skip to content

Commit

Permalink
Implements the function to issue notification using ntfy
Browse files Browse the repository at this point in the history
  • Loading branch information
hayashiya18 committed Sep 20, 2021
1 parent 4a3709a commit cb874d3
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 14 deletions.
13 changes: 13 additions & 0 deletions schema/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@
"title": "Trigger only for the last selected notebook cell execution.",
"description": "Trigger a notification only for the last selected executed notebook cell.",
"default": false
},
"notification_methods": {
"type": "array",
"minItems": 1,
"items": {
"enum": [
"browser",
"ntfy"
]
},
"title": "Notification Methods",
"description": "Methods how to notificate messages. Select from 'browser' or 'ntfy'",
"default": ["browser"]
}
}
}
57 changes: 43 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ISessionContext, SessionContext } from '@jupyterlab/apputils';
import { KernelError, Notebook, NotebookActions } from '@jupyterlab/notebook';
import { Cell } from '@jupyterlab/cells';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ICodeCellModel } from '@jupyterlab/cells';
import { PageConfig } from '@jupyterlab/coreutils';
import LRU from 'lru-cache';
import moment from 'moment';
import { issueNtfyNotification } from './ntfy';
import { checkBrowserNotificationSettings } from './settings';

interface ICellExecutionMetadata {
Expand All @@ -19,16 +21,18 @@ interface ICellExecutionMetadata {
/**
* Constructs notification message and displays it.
*/
function displayNotification(
async function displayNotification(
cellDuration: string,
cellNumber: number,
notebookName: string,
reportCellNumber: boolean,
reportCellExecutionTime: boolean,
failedExecution: boolean,
error: KernelError | null,
lastCellOnly: boolean
): void {
lastCellOnly: boolean,
notificationMethods: string[],
sessionContext: ISessionContext | null,
): Promise<void> {
const base = PageConfig.getBaseUrl();
const notificationPayload = {
icon: base + 'static/favicon.ico',
Expand All @@ -52,13 +56,20 @@ function displayNotification(
}

notificationPayload.body = message;
new Notification(title, notificationPayload);

if (notificationMethods.includes('browser')) {
new Notification(title, notificationPayload);
}
if ((notificationMethods.includes('ntfy')) && (sessionContext)) {
await issueNtfyNotification(title, notificationPayload, sessionContext);
}

}

/**
* Trigger notification.
*/
function triggerNotification(
async function triggerNotification(
cell: Cell,
notebook: Notebook,
cellExecutionMetadataTable: LRU<string, ICellExecutionMetadata>,
Expand All @@ -69,7 +80,9 @@ function triggerNotification(
cellNumberType: string,
failedExecution: boolean,
error: KernelError | null,
lastCellOnly: boolean
lastCellOnly: boolean,
notificationMethods: string[],
sessionContext: ISessionContext | null,
) {
const cellEndTime = new Date();
const codeCellModel = cell.model as ICodeCellModel;
Expand Down Expand Up @@ -102,15 +115,17 @@ function triggerNotification(
? cellExecutionMetadata.index
: codeCellModel.executionCount;
const notebookName = notebook.title.label.replace(/\.[^/.]+$/, '');
displayNotification(
await displayNotification(
cellDuration,
cellNumber,
notebookName,
reportCellNumber,
reportCellExecutionTime,
failedExecution,
error,
lastCellOnly
lastCellOnly,
notificationMethods,
sessionContext,
);
}
}
Expand All @@ -128,6 +143,8 @@ const extension: JupyterFrontEndPlugin<void> = {
let reportCellNumber = true;
let cellNumberType = 'cell_index';
let lastCellOnly = false;
let notificationMethods = ['browser'];

const cellExecutionMetadataTable: LRU<
string,
ICellExecutionMetadata
Expand All @@ -138,6 +155,13 @@ const extension: JupyterFrontEndPlugin<void> = {
max: 500
});

// SessionContext is used for running python codes
const manager = app.serviceManager;
const sessionContext = new SessionContext({
sessionManager: manager.sessions as any,
specsManager: manager.kernelspecs,
});

if (settingRegistry) {
const setting = await settingRegistry.load(extension.id);
const updateSettings = (): void => {
Expand All @@ -150,6 +174,7 @@ const extension: JupyterFrontEndPlugin<void> = {
.composite as boolean;
cellNumberType = setting.get('cell_number_type').composite as string;
lastCellOnly = setting.get('last_cell_only').composite as boolean;
notificationMethods = setting.get('notification_methods').composite as string[];
};
updateSettings();
setting.changed.connect(updateSettings);
Expand All @@ -165,10 +190,10 @@ const extension: JupyterFrontEndPlugin<void> = {
}
});

NotebookActions.executed.connect((_, args) => {
NotebookActions.executed.connect(async (_, args) => {
if (enabled && !lastCellOnly) {
const { cell, notebook, success, error } = args;
triggerNotification(
await triggerNotification(
cell,
notebook,
cellExecutionMetadataTable,
Expand All @@ -179,16 +204,18 @@ const extension: JupyterFrontEndPlugin<void> = {
cellNumberType,
!success,
error,
lastCellOnly
lastCellOnly,
notificationMethods,
sessionContext,
);
}
});

NotebookActions.selectionExecuted.connect((_, args) => {
NotebookActions.selectionExecuted.connect(async (_, args) => {
if (enabled && lastCellOnly) {
const { lastCell, notebook } = args;
const failedExecution = false;
triggerNotification(
await triggerNotification(
lastCell,
notebook,
cellExecutionMetadataTable,
Expand All @@ -199,7 +226,9 @@ const extension: JupyterFrontEndPlugin<void> = {
cellNumberType,
failedExecution,
null,
lastCellOnly
lastCellOnly,
notificationMethods,
sessionContext,
);
}
});
Expand Down
35 changes: 35 additions & 0 deletions src/ntfy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ISessionContext } from '@jupyterlab/apputils';
import { Kernel, KernelAPI, KernelMessage } from '@jupyterlab/services';

export async function ensureSessionContextKernelActivated(
sessionContext: ISessionContext
): Promise<void> {
if (sessionContext.hasNoKernel) {
await sessionContext.initialize()
.then(async (value) => {
if (value) {
const py3kernel = await KernelAPI.startNew({ name: 'python3' });
await sessionContext.changeKernel(py3kernel);
}
})
.catch((reason) => {
console.error(`Failed to initialize the session in jupyterlab-notifications.\n${reason}`);
});;
}
}

export async function issueNtfyNotification(
title: string,
notificationPayload: { body: string },
sessionContext: ISessionContext,
): Promise<Kernel.IShellFuture<KernelMessage.IExecuteRequestMsg, KernelMessage.IExecuteReplyMsg>> {
const { body } = notificationPayload;
await ensureSessionContextKernelActivated(sessionContext);
if (!sessionContext || !sessionContext.session?.kernel) {
return;
}
const titleEscaped = title.replace(/"/g, '\\"');
const bodyEscaped = body.replace(/"/g, '\\"');
const code = `from ntfy import notify; notify("${bodyEscaped}", "${titleEscaped}")`;
return sessionContext.session?.kernel?.requestExecute({ code });
}

0 comments on commit cb874d3

Please sign in to comment.