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

DATAUP-266 staging refresh #1886

Merged
merged 7 commits into from
Oct 23, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions kbase-extension/static/kbase/css/kbaseNarrative.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@
cursor: pointer;
}

.kb-data-staging__container {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

height: 604px;
padding: 5px;
overflow-y: auto;
}

.kb-data-staging-footer {
font-family: Oxygen, Arial, sans-serif;
font-weight: bold;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ define([
return new KBWidget({
name: 'kbaseNarrativeStagingDataTab',
$myFiles: $('<div>'),
minRefreshTime: 1000, // minimum ms required before letting updateView do its update
lastRefresh: 0, // the last time (in ms since epoch) that updateView was run
updateTimeout: null, // a Timeout that reconciles the above times (resets to null)

init: function(options) {
this._super(options);
this.path = '/';
},

getUserInfo: () => {
let auth = Auth.make({url: Config.url('auth')});
var userInfo;
getUserInfo: function() {
const auth = Auth.make({url: Config.url('auth')});
let userInfo;
return auth.getCurrentProfile(auth.getAuthToken())
.then(info => {
userInfo = {
Expand All @@ -36,24 +39,24 @@ define([
};
return userInfo;
})
.catch((err) => {
.catch(() => {
console.error('An error occurred while determining whether the user account is linked to Globus. Continuing without links.');
userInfo = {
user: Jupyter.narrative.userId,
globusLinked: false
}
};
return userInfo;
})
.finally(() => {
return userInfo;
});
},

activate : function() {
activate: function() {
this.stagingAreaViewer.activate();
},

deactivate : function() {
deactivate: function() {
this.stagingAreaViewer.deactivate();
},

Expand All @@ -65,38 +68,69 @@ define([

render: function() {
return this.getUserInfo()
.then(userInfo => {
var $mainElem = $('<div>')
.css({
'height': '604px',
'padding': '5px',
'overflow-y': 'auto'
.then(userInfo => {
const $mainElem = $('<div>')
ialarmedalien marked this conversation as resolved.
Show resolved Hide resolved
.addClass('kb-data-staging__container');
const $dropzoneElem = $('<div>');
this.$elem
.empty()
.append($mainElem
.append($dropzoneElem)
.append(this.$myFiles));

this.uploadWidget = new FileUploadWidget($dropzoneElem, {
path: this.path,
userInfo: userInfo,
userId: Jupyter.narrative.userId
});
var $dropzoneElem = $('<div>');
this.$elem
.empty()
.append($mainElem
.append($dropzoneElem)
.append(this.$myFiles));

this.uploadWidget = new FileUploadWidget($dropzoneElem, {
path: this.path,
userInfo: userInfo,
userId: Jupyter.narrative.userId
});
this.uploadWidget.dropzone.on('complete', () => {
this.updateView();
});

this.stagingAreaViewer = new StagingAreaViewer(this.$myFiles, {
path: this.path,
updatePathFn: this.updatePath.bind(this),
userInfo: userInfo
});
this.stagingAreaViewer = new StagingAreaViewer(this.$myFiles, {
path: this.path,
updatePathFn: this.updatePath.bind(this),
userInfo: userInfo
});

this.updateView();
});
this.updateView();
});
},

/**
* This updates the staging area viewer, and is called whenever an upload finishes.
* In addition, this should only fire a refresh event once per second (or some interval)
* to avoid spamming the staging area service and locking up the browser.
*
* So, when this is called the first time, it tracks the time it was called.
* If the next time this is called is less than some minRefreshTime apart, this
* makes a timeout with the time difference.
*/
updateView: function() {
this.stagingAreaViewer.render();
// this does the staging area re-render, then tracks the time
// it was last done.
const renderStagingArea = () => {
this.stagingAreaViewer.render();
this.lastRefresh = new Date().getTime();
};

// See how long it's been since the last refresh.
const refreshDiff = new Date().getTime() - this.lastRefresh;
if (refreshDiff < this.minRefreshTime) {
// if it's been under the minimum refresh time, and we're not already sitting on
// a pending timeout, then make one.
// If there IS a pending timeout, then do nothing, and it'll refresh when that fires.
if (!this.updateTimeout) {
this.updateTimeout = setTimeout(() => {
renderStagingArea();
this.updateTimeout = null;
}, refreshDiff);
}
}
else {
renderStagingArea();
}
}
});
});
2 changes: 1 addition & 1 deletion src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
"google_ad_conversion": "kR9OCLas4JgBEOy2pucC"
},
"comm_wait_timeout": 600000,
"config": "narrative-refactor",
"config": "dev",
"data_panel": {
"initial_sort_limit": 10000,
"max_name_length": 33,
Expand Down
129 changes: 102 additions & 27 deletions test/unit/spec/narrative_core/kbaseNarrativeStagingDataTab-spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/*global define*/
/*global describe, it, expect*/
/*global jasmine*/
/*global beforeEach, afterEach*/
/*global jasmine, describe, it, expect, beforeEach, afterEach, spyOn*/
/*jslint white: true*/
define([
'jquery',
Expand All @@ -14,12 +12,10 @@ define([
) {
'use strict';
describe('Test the kbaseNarrativeStagingDataTab widget', () => {
let $dummyNode = $('<div>'),
stagingWidget,
fakeUser = 'notAUser';
const fakeUser = 'notAUser';
beforeEach(() => {
jasmine.Ajax.install();
jasmine.Ajax.stubRequest("https://ci.kbase.us/services/auth/api/V2/me").andReturn({
jasmine.Ajax.stubRequest(/\/auth\/api\/V2\/me$/).andReturn({
status: 200,
statusText: 'success',
contentType: 'application/json',
Expand All @@ -42,14 +38,14 @@ define([
responseHeaders: '',
responseText: JSON.stringify([
{
name: "test_folder",
path: fakeUser + "/test_folder",
name: 'test_folder',
path: fakeUser + '/test_folder',
mtime: 1532738637499,
size: 34,
isFolder: true
}, {
name: "file_list.txt",
path: fakeUser + "/test_folder/file_list.txt",
name: 'file_list.txt',
path: fakeUser + '/test_folder/file_list.txt',
mtime: 1532738637555,
size: 49233,
source: 'KBase upload'
Expand All @@ -60,30 +56,109 @@ define([
userId: fakeUser,
getAuthToken: () => { return 'fakeToken'; }
};
stagingWidget = new StagingDataTab($dummyNode);
});

afterEach(() => {
jasmine.Ajax.uninstall();
$dummyNode.remove();
});

it('can load properly', (done) => {
stagingWidget.render()
.then(() => {
expect(stagingWidget).not.toBeNull();
done();
});
it('can load properly', async () => {
const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
await stagingWidget.render();
expect(stagingWidget).not.toBeNull();
});

it('can update its path properly', (done) => {
stagingWidget.render()
.then(() => {
var newPath = 'a_new_path';
stagingWidget.updatePath(newPath);
expect(stagingWidget.path).toEqual(newPath);
done();
});
it('properly catches failed user profile lookups by returning a default unlinked profile', async () => {
jasmine.Ajax.stubRequest(/\/auth\/api\/V2\/me$/).andReturn({
status: 500,
statusText: 'error',
contentType: 'text/html',
responseHeaders: '',
responseText: 'error! no profile for you!'
});
const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
const userInfo = await stagingWidget.getUserInfo();
expect(userInfo).toEqual({user: fakeUser, globusLinked: false});
});

it('gets user info and parsed into whether or not the user is linked to globus', async () => {
const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
const userInfo = await stagingWidget.getUserInfo();
expect(userInfo).toEqual({user: fakeUser, globusLinked: true});
});
Comment on lines +86 to +91
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra tests! 🎉


it('can update its path properly', async () => {
const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
await stagingWidget.render();
const newPath = 'a_new_path';
stagingWidget.updatePath(newPath);
expect(stagingWidget.path).toEqual(newPath);
});

it('can activate its staging area viewer', async () => {
const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
await stagingWidget.render();
spyOn(stagingWidget.stagingAreaViewer, 'activate');
stagingWidget.activate();
expect(stagingWidget.stagingAreaViewer.activate).toHaveBeenCalled();
});

it('can deactivate its staging area viewer', async () => {
const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
await stagingWidget.render();
spyOn(stagingWidget.stagingAreaViewer, 'deactivate');
stagingWidget.activate();
stagingWidget.deactivate();
expect(stagingWidget.stagingAreaViewer.deactivate).toHaveBeenCalled();
});

it('can be told to update its view', async () => {
jasmine.clock().install();

const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
await stagingWidget.render();
// kinda cheating - but to test that things are run, we know that this will call
// stagingAreaViewer.render
spyOn(stagingWidget.stagingAreaViewer, 'render');
stagingWidget.updateView();
jasmine.clock().tick(stagingWidget.minRefreshTime + 100);
expect(stagingWidget.stagingAreaViewer.render).toHaveBeenCalled();

jasmine.clock().uninstall();
});

it('can be triggered to update its view after a completed upload', async () => {
const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
await stagingWidget.render();
spyOn(stagingWidget, 'updateView');
// a little more cheating with implementation, this triggers the "upload complete" event
stagingWidget.uploadWidget.dropzone.emit('complete', {name: 'foo', size: 12345});
expect(stagingWidget.updateView).toHaveBeenCalled();
});

it('only updates its view once per interval on completed uploads', async () => {
jasmine.clock().install();

const $dummyNode = $('<div>'),
stagingWidget = new StagingDataTab($dummyNode);
await stagingWidget.render();
// run a bunch of triggers, should only call render on the staging area once
spyOn(stagingWidget.stagingAreaViewer, 'render');
for (let i = 0; i < 100; i++) {
stagingWidget.uploadWidget.dropzone.emit('complete', {name: 'foo', size: 12345});
}
jasmine.clock().tick(stagingWidget.minRefreshTime + 100);
expect(stagingWidget.stagingAreaViewer.render.calls.count()).toEqual(1);

jasmine.clock().uninstall();
});
});
});