Skip to content

Commit ad78456

Browse files
authored
Merge pull request #85 from ITC-CRIB/update-remote
Push command
2 parents 7de69c5 + 9b13b10 commit ad78456

File tree

3 files changed

+156
-14
lines changed

3 files changed

+156
-14
lines changed

jupyter_fairly/jupyter_fairly/handlers.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -257,25 +257,79 @@ def post(self):
257257
"message": 'completed',
258258
}))
259259

260+
261+
class PushDataset(APIHandler):
262+
"""
263+
Handler for pushing updates on files and metadata to data repository
264+
"""
265+
266+
@tornado.web.authenticated
260267
def patch(self):
261-
""" Send updates on files and metadata to remore repository
268+
""" Updates files and metadata in existing dataset in data repository
262269
263270
Args:
264271
265-
local-dataset (str): path to root directory of initialized fairly dataset
272+
localdataset (str): path to root directory of initialized fairly dataset
266273
witch a remote registered in the manifest.yaml file
267274
268275
Body example as JSON:
269276
{
270277
271-
"local-dataset": <path to root directory of fairly dataset>
278+
"localdataset": <path to root directory of fairly dataset>
272279
}
273280
"""
274281

275282
data = self.get_json_body()
283+
print(data)
276284

277285
try:
278-
local_dataset = fairly.dataset(data["local-dataset"])
286+
local_dataset = fairly.dataset(data["localdataset"])
287+
288+
except FileNotFoundError as e:
289+
raise web.HTTPError(404, f"Manifest file is missing from current directory: {e}")
290+
except NotADirectoryError as e:
291+
raise web.HTTPError(404, f"Path to dataset is not a directory: {e}")
292+
293+
try:
294+
local_dataset.push() # push updates (files and metadata) to remote repository
295+
except ValueError:
296+
raise web.HTTPError(405, f"The dataset doesn't have a remote. Make sure you're in the right directory or use `upload` option first.")
297+
except KeyError as e:
298+
raise web.HTTPError(400, f"Possible malformed manifest file. Missing {e}")
299+
else:
300+
self.finish(json.dumps({
301+
"message": 'remote dataset is up to date',
302+
}))
303+
304+
305+
class PullDataset(APIHandler):
306+
"""
307+
Handler for pulling updates on files and metadata to remore repository
308+
"""
309+
310+
@tornado.web.authenticated
311+
def patch(self):
312+
""" Updates files and metadata in local dataset based on changes in data repository.
313+
314+
Args:
315+
316+
localdataset (str): path to root directory of initialized fairly dataset
317+
witch a remote registered in the manifest.yaml file
318+
319+
Body example as JSON:
320+
{
321+
322+
"localdataset": <path to root directory of fairly dataset>
323+
}
324+
"""
325+
raise web.HTTPError(501, "Not implemented yet")
326+
327+
# TODO: implement save changes to manifest.yaml and files in local dataset
328+
data = self.get_json_body()
329+
print(data)
330+
331+
try:
332+
local_dataset = fairly.dataset(data["localdataset"])
279333

280334
except FileNotFoundError as e:
281335
raise web.HTTPError(404, f"Manifest file is missing from current directory: {e}")
@@ -287,8 +341,10 @@ def patch(self):
287341
except ValueError:
288342
raise web.HTTPError(405, f"The dataset doesn't have a remote. Use the upload option first.")
289343
else:
344+
# save changes to manifest.yaml
345+
290346
self.finish(json.dumps({
291-
"message": 'remote was updated',
347+
"message": 'local dataset is up to date',
292348
}))
293349

294350

@@ -355,6 +411,8 @@ def setup_handlers(web_app):
355411
initialize_dataset_url = url_path_join(extension_url, "newdataset")
356412
clone_dataset_url = url_path_join(extension_url, "clone")
357413
upload_dataset_url = url_path_join(extension_url, "upload")
414+
push_dataset_url = url_path_join(extension_url, "push")
415+
pull_dataset_url = url_path_join(extension_url, "pull")
358416
register_token_url = url_path_join(extension_url, "repo-token")
359417

360418

@@ -364,6 +422,8 @@ def setup_handlers(web_app):
364422
(initialize_dataset_url, InitFairlyDataset),
365423
(clone_dataset_url, CloneDataset),
366424
(upload_dataset_url, UploadDataset),
425+
(push_dataset_url, PushDataset),
426+
(pull_dataset_url, PullDataset),
367427
(register_token_url, RegisterRepositoryToken)
368428
]
369429

jupyter_fairly/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jupyter-fairly",
3-
"version": "0.3.1",
3+
"version": "0.4.0",
44
"description": "A JupyterLab extension for seamless integration of Jupyter-based research environments and research data repositories",
55
"keywords": [
66
"jupyter",

jupyter_fairly/src/upload.ts

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55

66
import {
77
InputDialog,
8-
Notification
8+
Notification,
9+
showDialog,
10+
Dialog
911
} from '@jupyterlab/apputils';
1012

1113
import {
@@ -17,13 +19,11 @@ import { PromiseDelegate, ReadonlyJSONValue } from '@lumino/coreutils';
1719
// Icons
1820
import {
1921
fileUploadIcon,
22+
redoIcon
2023
} from '@jupyterlab/ui-components';
2124
import { requestAPI } from './handler';
2225
import { showErrorMessage } from '@jupyterlab/apputils';
2326

24-
/**
25-
* Uploads metadata and files to data repository
26-
*/
2727

2828

2929
function uploadDataset(directory: string, repository: string) {
@@ -88,6 +88,54 @@ import { showErrorMessage } from '@jupyterlab/apputils';
8888
};
8989

9090

91+
function pushDataset(localDataset: string) {
92+
/**
93+
* upload local dataset to data reposotory
94+
* @param localDataset - realtive path to directory of local dataset with remote metadata
95+
*/
96+
97+
/* ./ is necessary becaucause defaultBrowser.Model.path
98+
* returns an empty string when fileBlowser is on the
99+
* jupyterlab root directory
100+
*/
101+
let rootPath = './';
102+
103+
let payload = JSON.stringify({
104+
localdataset: rootPath.concat(localDataset)
105+
});
106+
107+
// notification
108+
const delegate = new PromiseDelegate<ReadonlyJSONValue>();
109+
const complete = "complete";
110+
const failed = "failed"
111+
112+
requestAPI<any>('push', {
113+
method: 'PATCH',
114+
body: payload
115+
})
116+
.then(data => {
117+
console.log(data);
118+
delegate.resolve({ complete });
119+
})
120+
.catch(reason => {
121+
delegate.reject({ failed });
122+
// show error when
123+
showErrorMessage("Error when updating remote dataset", reason)
124+
});
125+
126+
Notification.promise(delegate.promise, {
127+
pending: { message: 'Pushing dataset to repository ...', options: { autoClose: false } },
128+
success: {
129+
message: (result: any) =>
130+
`Remote dataset update ${result.complete}.`,
131+
options: {autoClose: 3000}
132+
},
133+
error: {message: () => `Pushing has failed.`}
134+
});
135+
136+
};
137+
138+
91139
export const uploadDatasetPlugin: JupyterFrontEndPlugin<void> = {
92140
id: '@jupyter-fairly/upload',
93141
requires: [IFileBrowserFactory],
@@ -102,8 +150,9 @@ export const uploadDatasetPlugin: JupyterFrontEndPlugin<void> = {
102150
const fileBrowserModel = fileBrowser.model;
103151

104152

105-
const archiveDatasetCommand = "uploadDataset"
106-
app.commands.addCommand(archiveDatasetCommand, {
153+
// ** Upload a new dataset to a data repository **
154+
const uploadDatasetCommand = "uploadDataset"
155+
app.commands.addCommand(uploadDatasetCommand, {
107156
label: 'Upload Dataset',
108157
isEnabled: () => true,
109158
isVisible: () => true, // activate only when current directory contains a manifest.yalm
@@ -144,11 +193,44 @@ export const uploadDatasetPlugin: JupyterFrontEndPlugin<void> = {
144193
}
145194
});
146195

196+
// ** Push changes made to a local dataset to a data repository **
197+
const pushCommand = "pushDataset"
198+
app.commands.addCommand(pushCommand, {
199+
label: 'Push',
200+
isEnabled: () => true,
201+
isVisible: () => true, // activate only when current directory contains a manifest.yalm
202+
icon: redoIcon,
203+
execute: async() => {
204+
205+
let confirmAction = await showDialog({
206+
title: 'Push changes', // Can be text or a react element
207+
body: 'This will update the data repository using changes made here.',
208+
host: document.body, // Parent element for rendering the dialog
209+
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Push' })],
210+
})
211+
212+
if (confirmAction.button.accept){
213+
await pushDataset(fileBrowserModel.path)
214+
} else {
215+
console.log('rejected');
216+
return
217+
};
218+
}
219+
});
220+
147221
app.contextMenu.addItem({
148-
command: archiveDatasetCommand,
222+
command: uploadDatasetCommand,
149223
// matches anywhere in the filebrowser
150224
selector: '.jp-DirListing-content',
151225
rank: 104
152-
});
226+
}
227+
);
228+
app.contextMenu.addItem({
229+
command: pushCommand,
230+
// matches anywhere in the filebrowser
231+
selector: '.jp-DirListing-content',
232+
rank: 105
233+
}
234+
);
153235
}
154236
};

0 commit comments

Comments
 (0)