Commit eec1e964 authored by Mark Ghiorso's avatar Mark Ghiorso

Revised code for version 0.3.1 of JupyterLab

- Brought code up to conform to commit a4bc663a8df455bae3564eb53a1d812ffaaabf5b of 12/24/17 for the JupyterLab-github extension project
- raised version to 0.2.0
- installs and runs under JupyterLab release condidate rc1 of 0.3.1
parent 588fc83c
# Jupyterlab GitLab
# JupyterLab GitLab
A JupyterLab extension for accessing GitLab repositories.
......@@ -20,7 +20,7 @@ be added in a subsequent release.
## Prerequisites
* JupyterLab 0.29
* JupyterLab 0.31
* A GitLab account for accessing non-public repositories
## Installation
......
This diff is collapsed.
{
"name": "jupyterlab_gitlab",
"version": "0.1.0",
"version": "0.2.0",
"description": "A JupyterLab extension to access GitLab repositories",
"license": "BSD-3-Clause",
"author": "Mark Ghiorso",
"main": "lib/index.js",
"keywords": [
"gitlab",
"jupyter",
"jupyterlab"
],
"homepage": "https://gitlab.com/ENKI-portal/jupyterlab_gitlab",
"bugs": {
"url": "https://gitlab.com/ENKI-portal/jupyterlab_gitlab/issues"
},
"scripts": {
"build": "tsc",
"clean": "rimraf lib",
......@@ -22,12 +28,20 @@
"extension": true
},
"dependencies": {
"@jupyterlab/application": "^0.12.0",
"@jupyterlab/docmanager": "^0.12.0",
"@jupyterlab/filebrowser": "^0.12.0"
"@jupyterlab/application": "^0.14.0",
"@jupyterlab/apputils": "^0.14.0",
"@jupyterlab/coreutils": "^1.0.1",
"@jupyterlab/docmanager": "^0.14.0",
"@jupyterlab/docregistry": "^0.14.0",
"@jupyterlab/filebrowser": "^0.14.0",
"@jupyterlab/observables": "^1.0.1",
"@jupyterlab/services": "^1.0.1",
"@phosphor/algorithm": "^1.1.2",
"@phosphor/signaling": "^1.2.2",
"@phosphor/widgets": "^1.5.0"
},
"devDependencies": {
"rimraf": "^2.6.1",
"typescript": "~2.4.0"
"rimraf": "^2.6.2",
"typescript": "~2.6.2"
}
}
......@@ -6,7 +6,7 @@ import setuptools
setuptools.setup(
name='jupyterlab_gitlab',
description='A Jupyter Notebook server extension which acts as a proxy for GitLab API requests.',
version='0.1.0',
version='0.2.0',
packages=setuptools.find_packages(),
author = 'Mark S. Ghiorso, based on Jupyter Development Team. GitHub',
author_email = 'ghiorso@ofm-research.org',
......
......@@ -7,9 +7,13 @@ import {
} from '@jupyterlab/apputils';
import {
ObservableValue, URLExt
URLExt
} from '@jupyterlab/coreutils';
import {
ObservableValue
} from '@jupyterlab/observables';
import {
FileBrowser
} from '@jupyterlab/filebrowser';
......@@ -163,6 +167,7 @@ class GitLabFileBrowser extends Widget {
this._errorPanel = new GitLabErrorPanel(message);
const listing = (this._browser.layout as PanelLayout).widgets[2];
listing.node.appendChild(this._errorPanel.node);
return;
}
}
......@@ -186,7 +191,7 @@ class GitLabEditableName extends Widget {
this._editNode = document.createElement('input');
this._editNode.className = 'jp-GitLabEditableName-input';
this._placeholder = placeholder || '<Edit Name>'
this._placeholder = placeholder || '<Edit Name>';
this.node.appendChild(this._nameNode);
this.name = new ObservableValue(initialName);
......@@ -266,7 +271,7 @@ namespace Private {
function changeField(text: HTMLElement, edit: HTMLInputElement): Promise<string> {
// Replace the text node with an the input element.
let parent = text.parentElement as HTMLElement;
let initialValue = text.textContent;
let initialValue = text.textContent || '';
edit.value = initialValue;
parent.replaceChild(edit, text);
edit.focus();
......
......@@ -6,6 +6,10 @@ import {
PathExt, URLExt
} from '@jupyterlab/coreutils';
//import {
// ObservableValue
//} from '@jupyterlab/observables';
import {
DocumentRegistry
} from '@jupyterlab/docregistry';
......@@ -35,7 +39,7 @@ class GitLabDrive implements Contents.IDrive {
*/
constructor(registry: DocumentRegistry) {
//console.warn('Entering constructor GitLabDrive');
this._serverSettings = ServerConnection.makeSettings();
//this._serverSettings = ServerConnection.makeSettings();
this._fileTypeForPath = (path: string) => {
const types = registry.getFileTypesForPath(path);
return types.length === 0 ?
......@@ -143,22 +147,22 @@ class GitLabDrive implements Contents.IDrive {
return this._apiRequest<GitLabContents>(apiPath).then(contents => {
this._validUser = true;
return Private.gitLabContentsToJupyterContents(path, contents, this._fileTypeForPath);
}).catch(response => {
if(response.xhr.status === 404) {
}).catch((err: ServerConnection.ResponseError) => {
if (err.response.status === 404) {
//console.warn('GitLab: cannot find org/repo. Perhaps you misspelled something?');
this._validUser = false;
return Private.DummyDirectory;
} else if (response.xhr.status === 403 &&
response.xhr.responseText.indexOf('rate limit') !== -1) {
console.error(response.message);
return Promise.reject(response);
} else if (response.xhr.status === 403 &&
response.xhr.responseText.indexOf('blob') !== -1) {
} else if (err.response.status === 403 &&
err.message.indexOf('rate limit') !== -1) {
console.error(err.message);
return Promise.reject(err);
} else if (err.response.status === 403 &&
err.message.indexOf('blob') !== -1) {
this._validUser = true;
return this._getBlob(path);
} else {
console.error(response.message);
return Promise.reject(response);
console.error(err.message);
return Promise.reject(err);
}
});
}
......@@ -201,6 +205,7 @@ class GitLabDrive implements Contents.IDrive {
return item.path;
}
}
throw Private.makeError(404, `Cannot find file at ${resource.path}`);
});
}
......@@ -379,9 +384,9 @@ class GitLabDrive implements Contents.IDrive {
this._apiRequest<GitLabRepo[]>(apiPath).then(repos => {
this._validUser = true;
resolve(Private.reposToDirectory(repos));
}).catch((response) => {
if (response.xhr.status === 403 &&
response.xhr.responseText.indexOf('rate limit') !== -1) {
}).catch((err: ServerConnection.ResponseError) => {
if (err.response.status === 403 &&
err.message.indexOf('rate limit') !== -1) {
} else {
//console.warn('GitLab: cannot find user. Perhaps you misspelled something?');
this._validUser = false;
......@@ -399,7 +404,7 @@ class GitLabDrive implements Contents.IDrive {
}
private _validUser = false;
private _serverSettings: ServerConnection.ISettings;
//private _serverSettings: ServerConnection.ISettings;
private _fileTypeForPath: (path: string) => DocumentRegistry.IFileType;
private _isDisposed = false;
private _fileChanged = new Signal<this, Contents.IChangedArgs>(this);
......@@ -425,7 +430,7 @@ function parsePath(path: string): GitLabResource {
const user = parts.length > 0 ? parts[0] : '';
const repository = parts.length > 1 ? parts[1] : '';
const repoPath = parts.length > 2 ? URLExt.join(...parts.slice(2)) : '';
return { user, repository, path: repoPath }
return { user, repository, path: repoPath };
}
/**
......@@ -445,7 +450,7 @@ namespace Private {
created: '',
writable: false,
last_modified: '',
mimetype: null,
mimetype: '',
};
/**
......@@ -477,7 +482,7 @@ namespace Private {
writable: false,
created: '',
last_modified: '',
mimetype: null,
mimetype: '',
content: contents.map( c => {
return gitLabContentsToJupyterContents(
PathExt.join(path, c.name), c, fileTypeForPath);
......@@ -492,14 +497,16 @@ namespace Private {
let content: any;
switch (fileType.fileFormat) {
case 'text':
content = fileContents ? atob(fileContents) : null;
content = fileContents !== undefined ? atob(fileContents) : null;
break;
case 'base64':
content = fileContents || null;
content = fileContents !== undefined ? fileContents : null;
break;
case 'json':
content = fileContents ? JSON.parse(atob(fileContents)) : null;
content = fileContents !== undefined ? JSON.parse(atob(fileContents)) : null;
break;
default:
throw new Error(`Unexpected file format: ${fileType.fileFormat}`);
}
return {
name: PathExt.basename(path),
......@@ -511,7 +518,7 @@ namespace Private {
last_modified: '',
mimetype: fileType.mimeTypes[0],
content
}
};
} else if (contents.type === 'dir' || contents.type === 'tree') {
//console.warn('... a directory.');
// If it is a directory, convert to that.
......@@ -523,9 +530,9 @@ namespace Private {
created: '',
writable: false,
last_modified: '',
mimetype: null,
mimetype: '',
content: null
}
};
} else if (contents.type === 'submodule') {
// If it is a submodule, throw an error.
throw makeError(400, `Cannot open "${contents.name}" because it is a submodule`);
......@@ -557,7 +564,7 @@ namespace Private {
created: '',
writable: false,
last_modified: '',
mimetype: null,
mimetype: '',
content: null
} as Contents.IModel;
});
......@@ -570,26 +577,18 @@ namespace Private {
created: '',
last_modified: '',
writable: false,
mimetype: null,
mimetype: '',
content
};
}
/**
* Wrap an API error in a hacked-together error object
* masquerading as an `ServerConnection.IError`.
* masquerading as an `ServerConnection.ResponseError`.
*/
function makeError(code: number, message: string): ServerConnection.IError {
const xhr = {
status: code,
responseText: message
};
return {
event: undefined,
xhr: xhr as XMLHttpRequest,
ajaxSettings: null,
throwError: xhr.responseText,
message: xhr.responseText
} as any as ServerConnection.IError;
export
function makeError(code: number, message: string): ServerConnection.ResponseError {
const response = new Response(message, { status: code, statusText: message });
return new ServerConnection.ResponseError(response, message);
}
}
......@@ -32,36 +32,14 @@ function browserApiRequest<T>(url: string): Promise<T> {
}
}
return new Promise((resolve, reject) => {
const method = 'GET';
const requestUrl = URLExt.join(GITLab_API, url) + key;
let xhr = new XMLHttpRequest();
xhr.open(method, requestUrl);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response));
} else {
const err: ServerConnection.IError = {
xhr,
settings: undefined,
request: undefined,
event: undefined,
message: xhr.responseText
};
reject(err);
}
};
xhr.onerror = () => {
const err: ServerConnection.IError = {
xhr,
settings: undefined,
request: undefined,
event: undefined,
message: xhr.responseText
};
reject(err);
};
xhr.send();
const requestUrl = URLExt.join(GITLab_API, url) + key;
return window.fetch(requestUrl).then(response => {
if (response.status !== 200) {
return response.json().then(data => {
throw new ServerConnection.ResponseError(response, data.message);
});
}
return response.json();
});
}
......
......@@ -2,10 +2,6 @@ import {
ILayoutRestorer, JupyterLab, JupyterLabPlugin
} from '@jupyterlab/application';
import {
IStateDB
} from '@jupyterlab/coreutils';
import {
IDocumentManager
} from '@jupyterlab/docmanager';
......@@ -34,7 +30,7 @@ const NAMESPACE = 'gitLab-filebrowser';
*/
const fileBrowserPlugin: JupyterLabPlugin<void> = {
id: 'jupyterlab-gitLab:drive',
requires: [IDocumentManager, IFileBrowserFactory, ILayoutRestorer, IStateDB],
requires: [IDocumentManager, IFileBrowserFactory, ILayoutRestorer],
activate: activateFileBrowser,
autoStart: true
};
......@@ -42,7 +38,7 @@ const fileBrowserPlugin: JupyterLabPlugin<void> = {
/**
* Activate the file browser.
*/
function activateFileBrowser(app: JupyterLab, manager: IDocumentManager, factory: IFileBrowserFactory, restorer: ILayoutRestorer, state: IStateDB): void {
function activateFileBrowser(app: JupyterLab, manager: IDocumentManager, factory: IFileBrowserFactory, restorer: ILayoutRestorer): void {
const { commands } = app;
// Add the Google Drive backend to the contents manager.
......@@ -59,13 +55,15 @@ function activateFileBrowser(app: JupyterLab, manager: IDocumentManager, factory
gitLabBrowser.title.iconClass = 'jp-GitLab-tablogo';
gitLabBrowser.id = 'gitLab-file-browser';
/*
// See if we have an user cached in the IStateDB.
// Warning: there is a potential race condition here: if the filebrowser
// tries to restore its directory before the user is reset, we will
// overwrite that cwd. Otherwise we will not.
const id = NAMESPACE;
state.fetch(id).then(args => {
const user = (args && args['user'] as string) || '';
//const user = (args && args['user'] as string) || '';
const user = (args && (args as ReadonlyJSONObject)['user'] as string) || '';
gitLabBrowser.userName.name.set(user);
const code = (args && args['code'] as string) || '';
gitLabBrowser.userCode.name.set(code);
......@@ -75,7 +73,7 @@ function activateFileBrowser(app: JupyterLab, manager: IDocumentManager, factory
// Keep the IStateDB updated.
gitLabBrowser.userName.name.changed.connect((sender, args) => {
state.save(id, { user: args.newValue });
state.save(id, { user: args.newValue as string });
});
gitLabBrowser.userCode.name.changed.connect((sender, args) => {
state.save(id, { code: args.newValue });
......@@ -83,6 +81,7 @@ function activateFileBrowser(app: JupyterLab, manager: IDocumentManager, factory
gitLabBrowser.groupName.name.changed.connect((sender, args) => {
state.save(id, { group: args.newValue });
});
*/
// Add the file browser widget to the application restorer.
restorer.add(gitLabBrowser, NAMESPACE);
......
......@@ -4,11 +4,13 @@
"noImplicitAny": true,
"noEmitOnError": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"skipLibCheck": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "ES5",
"outDir": "./lib",
"lib": ["ES5", "ES2015.Promise", "DOM"],
"lib": ["ES5", "ES2015.Promise", "DOM", "ES2015.Collection"],
"types": []
},
"include": ["src/*"]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment