diff --git a/kbase-extension/static/kbase/css/kbaseNarrative.css b/kbase-extension/static/kbase/css/kbaseNarrative.css index 7ed67eab8a..3418d53fa3 100644 --- a/kbase-extension/static/kbase/css/kbaseNarrative.css +++ b/kbase-extension/static/kbase/css/kbaseNarrative.css @@ -5,12 +5,26 @@ font-weight: bold; } +.kb-data-staging-decompress { + border: 1px solid #cccccc; + border-radius : 1px; +} + .kb-data-staging-table-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.kb-data-staging-folder:hover { + cursor: pointer; + text-decoration: underline; +} + +.kb-pointer { + cursor: pointer; +} + .kb-data-staging-footer { font-family : Oxygen, Arial, sans-serif; font-weight: bold; diff --git a/kbase-extension/static/kbase/js/widgets/narrative_core/upload/stagingAreaViewer.js b/kbase-extension/static/kbase/js/widgets/narrative_core/upload/stagingAreaViewer.js index 3ef919b067..bd96ad5a62 100644 --- a/kbase-extension/static/kbase/js/widgets/narrative_core/upload/stagingAreaViewer.js +++ b/kbase-extension/static/kbase/js/widgets/narrative_core/upload/stagingAreaViewer.js @@ -43,6 +43,7 @@ define([ options: { refreshIntervalDuration: 30000, + path: '/' }, init: function (options) { @@ -272,7 +273,7 @@ define([ if (isFolder) { disp = ''; } else { - disp = " " + disp; + disp = ' ' + disp; } return disp; } else { @@ -284,11 +285,14 @@ define([ sClass: 'staging-name', mRender: function (data, type, full) { if (type === 'display') { - - var decompressButton = ''; + let decompressButton = ''; if (data.match(/\.(zip|tar\.gz|tgz|tar\.bz|tar\.bz2|tar|gz|bz2)$/)) { - decompressButton = " "; + decompressButton = ' '; + } + + if (full[0] === 'true') { + data = '' + data + ''; } return '
' + decompressButton + @@ -333,6 +337,10 @@ define([ hide: Config.get('tooltip').hideDelay } }); + $('td:eq(1)', nRow).find('span.kb-data-staging-folder').off('click').on('click', e => { + $(e.currentTarget).off('click'); + this.updatePathFn(this.path += '/' + $(e.currentTarget).data().name); + }); $('td:eq(4)', nRow).find('select').select2({ placeholder: 'Select format' }); @@ -367,6 +375,7 @@ define([ $('td:eq(0)', nRow).find('button[data-name]').off('click').on('click', e => { + $(e.currentTarget).off('click'); this.updatePathFn(this.path += '/' + $(e.currentTarget).data().name); }); diff --git a/test/unit/spec/narrative_core/upload/stagingAreaViewer-spec.js b/test/unit/spec/narrative_core/upload/stagingAreaViewer-spec.js index 0b51deb5f9..7a16f4ae72 100644 --- a/test/unit/spec/narrative_core/upload/stagingAreaViewer-spec.js +++ b/test/unit/spec/narrative_core/upload/stagingAreaViewer-spec.js @@ -10,21 +10,21 @@ define ([ 'base/js/namespace', 'kbaseNarrative', 'testUtil' -], function( +], ( $, StagingAreaViewer, Jupyter -) { +) => { 'use strict'; - describe('Test the staging area viewer widget', function() { + describe('Test the staging area viewer widget', () => { let stagingViewer, - $targetNode = $('
'), + $targetNode, startingPath = '/', - updatePathFn = function(newPath) { }, + updatePathFn = () => {}, fakeUser = 'notAUser'; - beforeEach(function() { + beforeEach(() => { jasmine.Ajax.install(); jasmine.Ajax.stubRequest(/.*\/staging_service\/list\/?/).andReturn({ status: 200, @@ -33,14 +33,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' @@ -49,7 +49,7 @@ define ([ }); Jupyter.narrative = { userId: fakeUser, - getAuthToken: () => { return 'fakeToken'; }, + getAuthToken: () => 'fakeToken', sidePanel: { '$dataWidget': { '$overlayPanel': {} @@ -59,9 +59,10 @@ define ([ } }, showDataOverlay: () => {}, - addAndPopulateApp: (id, tag, inputs) => {}, + addAndPopulateApp: () => {}, hideOverlay: () => {}, }; + $targetNode = $('
'); stagingViewer = new StagingAreaViewer($targetNode, { path: startingPath, updatePathFn: updatePathFn, @@ -78,16 +79,16 @@ define ([ stagingViewer = null; }); - it('Should initialize properly', function() { + it('Should initialize properly', () => { expect(stagingViewer).not.toBeNull(); }); - it('Should render properly', function() { + it('Should render properly', () => { stagingViewer.render(); expect(stagingViewer).not.toBeNull(); }); - it('Should render properly with a Globus linked account', (done) => { + it('Should render properly with a Globus linked account', async () => { let $node = $('
'), linkedStagingViewer = new StagingAreaViewer($node, { path: startingPath, @@ -97,36 +98,26 @@ define ([ globusLinked: true } }); - linkedStagingViewer.render() - .then(() => { - expect($node.html()).toContain('Or upload to this staging area by using'); - expect($node.html()).toContain('https://app.globus.org/file-manager?destination_id=c3c0a65f-5827-4834-b6c9-388b0b19953a&destination_path=' + fakeUser); - done(); - }); + await linkedStagingViewer.render(); + expect($node.html()).toContain('Or upload to this staging area by using'); + expect($node.html()).toContain('https://app.globus.org/file-manager?destination_id=c3c0a65f-5827-4834-b6c9-388b0b19953a&destination_path=' + fakeUser); }); it('Should render properly without a Globus linked account', () => { expect($targetNode.html()).not.toContain('Or upload to this staging area by using'); }); - it('Should start a help tour', function() { + it('Should start a help tour', () => { stagingViewer.render(); stagingViewer.startTour(); expect(stagingViewer.tour).not.toBeNull(); }); - it('Should update its view with a proper subpath', function(done) { - stagingViewer.updateView() - .then(function() { - done(); - }) - .catch(err => { - console.log(err); - fail(); - }); + it('Should update its view with a proper subpath', async () => { + await stagingViewer.updateView(); }); - it('Should show an error when a path does not exist', (done, fail) => { + it('Should show an error when a path does not exist', async () => { const errorText = 'An error occurred while fetching your files'; jasmine.Ajax.stubRequest(/.*\/staging_service\/list\/foo?/).andReturn({ status: 404, @@ -136,16 +127,11 @@ define ([ responseText: errorText }); - stagingViewer.setPath('//foo') - .then(() => { - expect($targetNode.find('.alert.alert-danger').html()).toContain(errorText); - // reset path. something gets cached with how async tests run. - stagingViewer.setPath('/'); - done(); - }); + await stagingViewer.setPath('//foo'); + expect($targetNode.find('.alert.alert-danger').html()).toContain(errorText); }); - it('Should show a "no files" next when a path has no files', (done) => { + it('Should show a "no files" next when a path has no files', async () => { jasmine.Ajax.stubRequest(/.*\/staging_service\/list\/empty?/).andReturn({ status: 200, statusText: 'success', @@ -154,13 +140,8 @@ define ([ responseText: JSON.stringify([]) }); - stagingViewer.setPath('//empty') - .then(() => { - expect($targetNode.find('#kb-data-staging-table').html()).toContain('No files found.'); - // reset path. something gets cached with how async tests run. - stagingViewer.setPath('/'); - done(); - }); + await stagingViewer.setPath('//empty'); + expect($targetNode.find('#kb-data-staging-table').html()).toContain('No files found.'); }); it('Should respond to activate and deactivate commands', () => { @@ -171,6 +152,34 @@ define ([ expect(stagingViewer.refreshInterval).toBeUndefined(); }); + it('Should have clickable folder icons', async () => { + spyOn(stagingViewer, 'updatePathFn'); + await stagingViewer.render(); + stagingViewer.$elem.find('button[data-name="test_folder"]').click(); + expect(stagingViewer.updatePathFn).toHaveBeenCalledWith('//test_folder'); + }); + + it('Should have clickable folder names', async () => { + spyOn(stagingViewer, 'updatePathFn'); + await stagingViewer.render(); + stagingViewer.$elem.find('span.kb-data-staging-folder').click(); + expect(stagingViewer.updatePathFn).toHaveBeenCalledWith('//test_folder'); + }); + + it('Should have multi-clicked folder buttons only fire once', async () => { + spyOn(stagingViewer, 'updatePathFn'); + await stagingViewer.render(); + stagingViewer.$elem.find('button[data-name]').click().click().click(); + expect(stagingViewer.updatePathFn).toHaveBeenCalledTimes(1); + }); + + it('Should have multi-clicked folder names only fire once', async () => { + spyOn(stagingViewer, 'updatePathFn'); + await stagingViewer.render(); + stagingViewer.$elem.find('span.kb-data-staging-folder').click().click().click(); + expect(stagingViewer.updatePathFn).toHaveBeenCalledTimes(1); + }); + it('Should initialize an import app with the expected inputs', () => { const fileType = 'fastq_reads', fileName = 'foobar.txt',