diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..081e2bac42 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +omit = + src/biokbase/narrative/tests/* diff --git a/.travis.yml b/.travis.yml index 2bee659d04..91febad63a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,8 @@ branches: - travis-firefox before_install: - - pip install virtualenv + - pip install virtualenv coveralls + - gem install coveralls-lcov - nvm install $TRAVIS_NODE_VERSION - npm install -g bower - npm install -g grunt-cli @@ -35,3 +36,7 @@ before_script: - sleep 3 # give xvfb some time to start script: make build-travis-narrative && make test + +after_script: + - coveralls-lcov -v -n js-coverage/lcov/lcov.info > js-coverage.json + - coveralls --merge js-coverage.json diff --git a/Gruntfile.js b/Gruntfile.js index 0dd3ba4e07..1297148a0d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -92,15 +92,15 @@ module.exports = function(grunt) { 'karma': { unit: { configFile: 'test/unit/karma.conf.js', - reporters: ['progress', 'coverage'], - coverageReporter: { - dir: 'build/test-coverage/', - reporters: [ - { - type: 'html', subdir: 'html' - } - ] - } + // reporters: ['progress'], //, 'coverage'], + // coverageReporter: { + // dir: 'build/test-coverage/', + // reporters: [ + // { + // type: 'html', subdir: 'html' + // } + // ] + // } }, dev: { // to do - add watch here diff --git a/Makefile b/Makefile index 0212163f1d..9afc01234d 100755 --- a/Makefile +++ b/Makefile @@ -40,7 +40,6 @@ test: test-backend test-frontend-unit test-frontend-e2e test-backend: @echo "running backend tests" sh $(BACKEND_TEST_SCRIPT) - # @echo "THIS IS A NO-OP UNTIL THE AUTH2 SWITCH" @echo "done" # test-frontend-unit should use karma and jasmine to test diff --git a/README.md b/README.md index 30b2347fff..752664e35d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ | Branch | Status | | :--- | :--- | | master | [![Build Status](https://travis-ci.org/kbase/narrative.svg?branch=master)](https://travis-ci.org/kbase/narrative) [![Coverage Status](https://coveralls.io/repos/kbase/narrative/badge.svg?branch=master)](https://coveralls.io/r/kbase/narrative?branch=master) | -| staging | [![Build Status](https://travis-ci.org/kbase/narrative.svg?branch=staging)](https://travis-ci.org/kbase/narrative) [![Coverage Status](https://coveralls.io/repos/kbase/narrative/badge.svg?branch=staging)](https://coveralls.io/r/kbase/narrative?branch=staging) | | develop | [![Build Status](https://travis-ci.org/kbase/narrative.svg?branch=develop)](https://travis-ci.org/kbase/narrative) [![Coverage Status](https://coveralls.io/repos/kbase/narrative/badge.svg?branch=develop)](https://coveralls.io/r/kbase/narrative?branch=develop)| This is the repository for the KBase Narrative Interface. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5ffc2a574c..030a62a60e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,32 @@ The Narrative Interface allows users to craft KBase Narratives using a combination of GUI-based commands, Python and R scripts, and graphical output elements. This is built on the Jupyter Notebook v4.4.1 (more notes will follow). +### Version 3.5.2 +- TASK-1089 - Import data slide out panel tracks what object is added to narrative. Add button turns into copy if objecct already exists. Add pop up when user copies and overrides exisiting object. +- TASK-1094 - Fix overlapping cells and buttons issue in Firefox +- TASK-1113/PUBLIC-148 - Import Panel scrolls if panel size is larger than screen size +- TASK-1088 - Data Pane maintains filters after refresh due to changes in narritve +- TASK-1114 - Add lock when editing name, that prevents data panel from refreshing with new data. Reliquenishes lock after 15 min if no activity. +- Style Fixes + - Fix bold font display inconsistencies between different browsers + - Move tooltip in datapanel from covering buttons to the top +- TASK-1158 - Standardize app and object cards in narrative and data panel + + +### Version 3.5.1 +- TASK-1117 - Add importer for FBAModels to staging area +- TASK-1116 - Add PhenotypeSet importer to staging area +- TASK-1089 - Import data slide out panel tracks what object is added to narrative. "Add" button turns into "copy" if object already exists. Add pop up when user copies and overrides existing object. +- TASK-1094 - Fix overlapping cells and buttons issue in Firefox +- TASK-1113/PUBLIC-148 - Import Panel scrolls if panel size is larger than screen size +- TASK-1088 - Data Pane maintains filters after refresh due to changes in narrative +- Style Fixes + - Fix bold font display inconsistencies between different browsers + - Move tooltip in data panel from covering buttons to the top +- KBASE-4756 - Fix data type filtering in data panel slideout. +- PTV-225 - Fixes sorting by type in data panel +- PTV-535 - Fix RNA-seq viewer to properly handle multiple input types. +- PUBLIC-123 - Fix incorrect reaction counts in FBA Model viewer ### Version 3.5.0 - TASK-1054 - Create a new loading window with a set of tasks to load and connect to (treats the problem of slowly loading websockets, still probably needs some adjusting). diff --git a/docs/Pavel's Custom Input Advanced View Widget.md b/docs/notes/Pavel's Custom Input Advanced View Widget.md similarity index 100% rename from docs/Pavel's Custom Input Advanced View Widget.md rename to docs/notes/Pavel's Custom Input Advanced View Widget.md diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000000..8cf8924be0 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,49 @@ +# Narrative Unit Testing + +### Short version +1. Install the Narrative and activate its virtualenv (if appropriate) + a. (optional) set credentials in test/ +2. Run `make test` at a prompt. +3. Lament the coverage is so low. + +### Long version +**About** +Because the Narrative Interface is built on both the front and back ends of the Jupyter Notebook, there's two sides to Narrative testing. The front end tests are in JavaScript, and make use of [Karma](http://karma-runner.github.io/1.0/index.html) as a test runner, and [Jasmine](https://jasmine.github.io/2.0/introduction.html) as a testing language. The back end tests are written in Python using the unittest framework, and run with [nose](http://nose.readthedocs.io/en/latest/). + +**How to run tests** +We aren't testing the underlying Jupyter code, but we are making use of it, so a test run requires a Narrative install as described in the [installation](../install/local_install.md) docs. If you're installing in a Virtualenv, make sure it's activated. You don't need to run the narrative yourself for tests to work, it just needs to be installed. + +Then, simply run (from the narrative root directory) `make test`. + +This calls a few subcommands, and those can be run independently for specific uses: + * `make test-frontend-unit` will run only the unit tests on the frontend (i.e. those with the Karma runner) + * `make test-frontend-e2e` will run only the frontend tests that make use of Selenium to simulate a browser on the real Narrative site. + * `make test-backend` will run only the backend Python tests. + +**Add credentials for tests** +The Narrative Interface is one of the hubs of KBase - it touches several different services, all of which need real authentication. Some of those have been mocked in various tests (like running apps), but others (the Workspace service) still require a real Auth token. If you have a KBase Developer account, you can create a Developer Token in the Account tab of the main KBase interface. + +You can store auth token files in test/. These are single line files, containing only the Auth token used for testing a single user. For example, `test/narrativetest.tok` would be for the narrativetest user, and would only contain that user's Auth token. DO NOT CHECK THESE IN TO GITHUB. + +Next, these credentials need to be referenced for both the back and front end. This version requires two configs - one for Python, and one for JavaScript. + +* Python: +`src/biokbase/narrative/tests/test.cfg` +In the `[users]` and `[token_files]` blocks, two sets of values are needed: test_user and private_user. They don't have any special permissions, they just need to be different users. + +* JavaScript: +`test/unit/testConfig.json` +This just needs the path to the token file (with pre-pended slash), such as `"/test/narrativetest.tok"` + +* *TODO (10/24/2017): Unify these token configs!* + +**Testing with Travis-CI / Coveralls** +These tests are run (without credentials) automatically on a pull request to the Narrative Github repo. These are currently run through [Travis-CI](https://travis-ci.org/) and the coverage reported with [Coveralls](https://coveralls.io/). There should be nothing you need to do to make this work. + +**Adding your own tests** + * **Python** + Python tests should be per module, and should all be added to the `src/biokbase/narrative/tests`. The `test.cfg` file there is in INI file format, and should be added to, as necessary. + + There are some service client Mocks available using the `mock` library. Check out `test_appmanager.py` for some examples of how these can be used. + * **JavaScript** + JavaScript tests follow the common Test Spec idiom. Here, we create a new spec file for each JavaScript module. These all live under `test/unit/spec` in roughly the same subdirectory as found under `kbase-extension/static/kbase/js`. There's an example spec in `test/unit/specTemplate.js` - you can just copy this to a new module, and modify to fit your needs. diff --git a/kbase-extension/static/custom/custom.css b/kbase-extension/static/custom/custom.css index 67d8aef3a0..a1f302f838 100644 --- a/kbase-extension/static/custom/custom.css +++ b/kbase-extension/static/custom/custom.css @@ -153,7 +153,11 @@ span#notebook_name:hover { right: 0; bottom: 0; overflow: auto; - padding-bottom: 100px; +} +#notebook-container:after{ + content: ""; + height: 100px; + display:block; } diff --git a/kbase-extension/static/kbase/config/staging_upload.json b/kbase-extension/static/kbase/config/staging_upload.json index 7eec402de9..9f21c69219 100644 --- a/kbase-extension/static/kbase/config/staging_upload.json +++ b/kbase-extension/static/kbase/config/staging_upload.json @@ -24,9 +24,17 @@ "id": "media", "name": "Media" }, + { + "id": "fba_model", + "name": "FBA Model" + }, { "id": "assembly", "name": "Assembly" + }, + { + "id": "phenotype_set", + "name": "Phenotype Set" } ], "app_info": { @@ -84,6 +92,15 @@ "app_output_suffix": "_media", "multiselect": false }, + "fba_model": { + "app_id": "kb_uploadmethods/import_file_as_fba_model_from_staging", + "app_input_param": "model_file", + "app_input_param_type": "string", + "app_static_params": {}, + "app_output_param": "model_name", + "app_output_suffix": "_model", + "multiselect": false + }, "assembly": { "app_id": "kb_uploadmethods/import_fasta_as_assembly_from_staging", "app_input_param": "staging_file_subdir_path", @@ -92,6 +109,15 @@ "app_output_param": "assembly_name", "app_output_suffix": "_assembly", "multiselect": false + }, + "phenotype_set": { + "app_id": "kb_uploadmethods/import_tsv_as_phenotype_set_from_staging", + "app_input_param": "staging_file_subdir_path", + "app_input_param_type": "string", + "app_static_params": {}, + "app_output_param": "phenotype_set_name", + "app_output_suffix": "_phenotype_set", + "multiselect": false } } } diff --git a/kbase-extension/static/kbase/css/kbaseNarrative.css b/kbase-extension/static/kbase/css/kbaseNarrative.css index ddb54aeca8..3ee7878aea 100644 --- a/kbase-extension/static/kbase/css/kbaseNarrative.css +++ b/kbase-extension/static/kbase/css/kbaseNarrative.css @@ -435,7 +435,7 @@ p#site-title { border-right: 5px solid #CECECE; top: 70px; bottom: 0; - overflow-y: auto; + /* overflow-y: auto; */ } .kb-side-overlay-container { @@ -444,6 +444,8 @@ p#site-title { z-index : 1030; background : white; position : fixed; + max-height: calc(100% - 73px); + overflow-y:auto; } .kb-side-overlay-header { @@ -466,8 +468,8 @@ p#site-title { } .kb-side-panel { - height: 100%; overflow-y: hidden; + height: 100%; } #kb-side-toggle-in { @@ -550,13 +552,21 @@ p#site-title { border-bottom: 5px solid #CECECE; } -.kb-side-tab { - display: none; -} + .kb-side-tab.active { display: inherit; } +.kb-side-tab { + display: none; + height:100%; +} +.kb-narr-side-panel-set{ + height:100%; +} +.kb-narr-side-panel-set:last-child{ + height:50%; +} .kb-overlay-dimmer { position: fixed; @@ -1030,6 +1040,7 @@ div#notebook_panel { .kb-narr-side-panel { margin-bottom: 5px; + height:100%; } .kb-narr-side-panel .kb-title { @@ -1190,8 +1201,18 @@ div#notebook_panel { } .kb-narr-panel-body { + height: calc(100%); + padding: 3px; +} +.kb-narr-panel-body > div{ + height: 100%; +} +.kb-narr-panel-body-wrapper { + height: calc(100% - 40px); padding: 3px; - /*overflow-y: auto;*/ +} +.kb-narr-panel-body-wrapper > div{ + height: 100%; } .kb-narr-panel-toggle { @@ -1206,9 +1227,8 @@ div#notebook_panel { /* Container for func list */ .kb-function-body { - height: 45%; - max-height:45%; - overflow-y: auto; + height: 100%; + /* overflow-y: auto; */ width: 100%; } @@ -2247,23 +2267,85 @@ button.kb-data-obj { font-size: 13px; } +/* card layout */ + +.narrative-card-logo{ + display: flex; + width: 50px; + margin-right: 2px; +} +.narrative-card-row-main{ + display: flex; + align-items: center; + width:98%; +} + +.narrative-data-list-subcontent{ + padding-left :10px; +} + +.narrative-card-ellipsis{ + display:flex; + align-items: center; + justify-content: center; + width:20px; +} + + +.narrative-card-palette-icon{ + position: relative; + color: '#888'; + left: 15px; + top: -50px; +} + + +.narrative-card-action-button-wrapper{ + width: 80px; + height: 50px; + margin: 0px 10px; +} + +.narrative-card-action-button{ + width: calc(100% - 6px); + height: 80%; + display: flex; + padding: 0px; + justify-content: center; +} + +.narrative-card-action-button > span{ + margin-right: 5px; +} +.narrative-card-action-button-name{ + display: inline-block; +} +.narrative-data-panel-btnToolbar > span{ + color: #888; +} +.narrative-data-panel-btnToolbar{ + display: flex; + justify-content: center; +} /* data list */ -.kb-data-list-obj-row { +.kb-data-list-obj-row, .narrative-card-row{ transition: all 0.1s ease; border-left-style: solid; border-left-width: 5px; border-left-color: #fff; color:#333; - margin:2px; - padding:4px; - margin-bottom: 5px; box-shadow: 1px 1px 1px 1px #fff; -webkit-box-shadow: 1px 1px 1px 1px #fff; moz-box-shadow: 1px 1px 1px 1px #fff; + +} +.narrative-card-row-main{ + margin:2px; + padding:4px; } .kb-function-dim .kb-data-list-obj-row { @@ -2271,7 +2353,7 @@ button.kb-data-obj { } -.kb-data-list-obj-row:hover { +.kb-data-list-obj-row:hover, .narrative-card-row:hover { border-left-style: solid; border-left-width: 5px; border-left-color: #4BB856; @@ -2482,11 +2564,14 @@ button.kb-data-obj { } .kb-data-list-info { + width: 80%; + padding: 4px 0px; + border-top: 1px solid #E0E0E0; } .kb-data-list-name { color:#2e618d; font-size: 11pt; - font-weight: bold; + font-family: 'OyxgenBold', Arial, sans-serif; margin: 2px; } .kb-data-list-name:hover { @@ -2497,21 +2582,18 @@ button.kb-data-obj { font-size: 9pt; font-style: italic; white-space: nowrap; - margin: 2px; cursor:default; } .kb-data-list-type { color:#777; font-size: 10pt; margin: 2px; - margin-left: 10px; cursor:default; } .kb-data-list-date { color:#777; font-size: 10pt; margin: 2px; - margin-left: 10px; white-space: nowrap; cursor:default; } @@ -2579,6 +2661,9 @@ button.kb-data-obj { color:#777; font-size: 10pt; margin: 2px; + display: flex; + flex-direction: column; + align-items: center; } .kb-data-list-more-div tr { color:#777; @@ -2747,23 +2832,25 @@ button.kb-data-obj { font-size: 0.9em; } -.kbcb-star { + +.kbcb-star-default{ + color:#888; } + .kbcb-star-favorite { - cursor:pointer; color:orange; } -/*.kbcb-star-favorite:hover { - color:inherit; -}*/ - -.kbcb-star-nonfavorite { +.kbcb-star-nonfavorite, .kbcb-star-favorite, .kbcb-star-default{ cursor:pointer; } -.kbcb-star-nonfavorite:hover { +.kbcb-star-nonfavorite:hover, .kbcb-star-default:hover { color:red; } +.kbcb-star-favorite:hover { + color:orange; +} + .kbcb-star-count { margin-left: 0.3em; display: inline-block; @@ -3003,9 +3090,9 @@ button.kb-data-obj { /*background-color: #2196F3; color: #BBDEFB;*/ color: #2196F3; - font-family: 'OxygenRegular', Arial, sans-serif; + font-family: 'OxygenBold', Arial, sans-serif; font-size: 16px; - font-weight: bold; + /* font-weight: bold; */ padding: 3px; margin-top: -3px; width: 147px; @@ -3031,9 +3118,9 @@ button.kb-data-obj { } /* this is the banner for readonly mode */ .navbar-right div.label-warning { - font-family: 'OxygenRegular', Arial, sans-serif; + font-family: 'OxygenBold', Arial, sans-serif; font-size: 16px; - font-weight: bold; + /* font-weight: bold; */ } /* Show this over the whole narrative UI until the diff --git a/kbase-extension/static/kbase/css/narrative.css b/kbase-extension/static/kbase/css/narrative.css index 996fae5537..dac3da9222 100644 --- a/kbase-extension/static/kbase/css/narrative.css +++ b/kbase-extension/static/kbase/css/narrative.css @@ -224,6 +224,8 @@ nav ul li.selected { color: #fff; } */ + + .search-box { position: relative; display: inline; @@ -329,14 +331,13 @@ nav ul li.selected { } /* left pane styling */ -#left-column { +/* #left-column { width: 289px; position: fixed; left: 10px; bottom: 0; top: 53px; - overflow-y: auto; -} +} */ #data-header { padding: 10px 0; diff --git a/kbase-extension/static/kbase/js/common/spec.js b/kbase-extension/static/kbase/js/common/spec.js index 72e833da01..c3e6f3136f 100644 --- a/kbase-extension/static/kbase/js/common/spec.js +++ b/kbase-extension/static/kbase/js/common/spec.js @@ -8,10 +8,10 @@ define([ 'require', 'bluebird', - './lang', - './sdk', - './specValidation', - '../widgets/appWidgets2/validators/resolver' + 'common/lang', + 'common/sdk', + 'common/specValidation', + 'widgets/appWidgets2/validators/resolver' ], function(require, Promise, lang, sdk, Validation, validationResolver) { 'use strict'; @@ -33,7 +33,7 @@ define([ /* * Make a "shell" model based on the spec. Recursively build an object * with properties as defined by the spec. - * Effectively this means that only the top level is represented, since + * Effectively this means that only the top level is represented, since */ function makeEmptyModel() { var model = {}; @@ -48,9 +48,9 @@ define([ from the given spec. It does this by: The top level spec is treated as a struct. - The default value for each paramater is simply set as the value for the given parameter + The default value for each paramater is simply set as the value for the given parameter on a model object. - One exception is that if a parameter is a + One exception is that if a parameter is a */ function makeDefaultedModel() { var model = {}; @@ -124,4 +124,4 @@ define([ return factory(config); } }; -}); \ No newline at end of file +}); diff --git a/kbase-extension/static/kbase/js/kbaseNarrative.js b/kbase-extension/static/kbase/js/kbaseNarrative.js index 7d118d005c..61d97b845c 100644 --- a/kbase-extension/static/kbase/js/kbaseNarrative.js +++ b/kbase-extension/static/kbase/js/kbaseNarrative.js @@ -126,6 +126,7 @@ define([ // The version of the currently loaded Narrative document object. this.documentVersionInfo = []; + this.stopVersionCheck = false; // this.dataViewers = null; @@ -227,6 +228,7 @@ define([ }); $([Jupyter.events]).on('notebook_saved.Notebook', function () { $('#kb-save-btn').find('div.fa-save').removeClass('fa-spin'); + self.stopVersionCheck = false; self.updateDocumentVersion(); }); $([Jupyter.events]).on('kernel_idle.Kernel', function () { @@ -329,7 +331,7 @@ define([ * an int > 0. */ Narrative.prototype.checkDocumentVersion = function (docInfo) { - if (docInfo.length < 5) { + if (docInfo.length < 5 || this.stopVersionCheck) { return; } if (docInfo[4] !== this.documentVersionInfo[4]) { @@ -737,8 +739,6 @@ define([ }.bind(this)) .finally(function () { this.sidePanel.render(); - this.updateDocumentVersion(); - // this.loadingWidget.remove(); }.bind(this)); $([Jupyter.events]).trigger('loaded.Narrative'); $([Jupyter.events]).on('kernel_ready.Kernel', @@ -794,6 +794,7 @@ define([ * This triggers a save, but saves all cell states first. */ Narrative.prototype.saveNarrative = function () { + this.stopVersionCheck = true; this.narrController.saveAllCellStates(); Jupyter.notebook.save_checkpoint(); this.toggleDocumentVersionBtn(false); diff --git a/kbase-extension/static/kbase/js/widgets/function_output/kbaseBinnedContigs.js b/kbase-extension/static/kbase/js/widgets/function_output/kbaseBinnedContigs.js index 42d8b08109..50241a9a7c 100644 --- a/kbase-extension/static/kbase/js/widgets/function_output/kbaseBinnedContigs.js +++ b/kbase-extension/static/kbase/js/widgets/function_output/kbaseBinnedContigs.js @@ -292,7 +292,7 @@ define([ title: 'Contig Length (bp)', }, yaxis2: { - title: 'GC Content (%)', + title: 'GC Content', range: [0, 1], side: 'right', overlaying: 'y', @@ -347,7 +347,7 @@ define([ new DynamicTable($content, { headers: [{ id: 'id', - text: 'Contig Id', + text: 'Contig ID', isSortable: true, }, { id: 'cov', diff --git a/kbase-extension/static/kbase/js/widgets/function_output/kbaseGenomeAnnotationAssembly.js b/kbase-extension/static/kbase/js/widgets/function_output/kbaseGenomeAnnotationAssembly.js index 5edb729bdd..864b405227 100644 --- a/kbase-extension/static/kbase/js/widgets/function_output/kbaseGenomeAnnotationAssembly.js +++ b/kbase-extension/static/kbase/js/widgets/function_output/kbaseGenomeAnnotationAssembly.js @@ -152,7 +152,7 @@ define ([ new DynamicTable($content, { headers: [{ id: 'contig_id', - text: 'Contig Id', + text: 'Contig ID', isSortable: true }, { id: 'length', @@ -160,7 +160,7 @@ define ([ isSortable: true }, { id: 'gc', - text: 'GC Content (%)', + text: 'GC Content', isSortable: true }], searchPlaceholder: 'Search contigs', diff --git a/kbase-extension/static/kbase/js/widgets/function_output/modeling/kbasePathways.js b/kbase-extension/static/kbase/js/widgets/function_output/modeling/kbasePathways.js index 2f7a945496..12514b083c 100644 --- a/kbase-extension/static/kbase/js/widgets/function_output/modeling/kbasePathways.js +++ b/kbase-extension/static/kbase/js/widgets/function_output/modeling/kbasePathways.js @@ -43,15 +43,15 @@ return KBWidget({ }}, { title: 'Map ID', mData: 1}, { title: 'Rxn Count', sWidth: '10%', mData: function(d){ - if ('reaction_ids' in d[10]){ - return d[10].reaction_ids.split(',').length; + if ('Number reactions' in d[10]){ + return d[10]['Number reactions']; } else { return 'N/A'; } }}, { title: 'Cpd Count', sWidth: '10%', mData: function(d) { - if ('compound_ids' in d[10]) { - return d[10].compound_ids.split(',').length; + if ('Number compounds' in d[10]) { + return d[10]['Number compounds']; } else { return 'N/A'; } diff --git a/kbase-extension/static/kbase/js/widgets/narrative_core/catalog/kbaseCatalogBrowser.js b/kbase-extension/static/kbase/js/widgets/narrative_core/catalog/kbaseCatalogBrowser.js index 22e6eca502..69b19f3a32 100644 --- a/kbase-extension/static/kbase/js/widgets/narrative_core/catalog/kbaseCatalogBrowser.js +++ b/kbase-extension/static/kbase/js/widgets/narrative_core/catalog/kbaseCatalogBrowser.js @@ -15,18 +15,20 @@ define ([ 'util/display', 'catalog-client-api', 'kbase-client-api', - 'kbaseAuthenticatedWidget' + 'kbaseAuthenticatedWidget', + 'base/js/namespace' ], function( - KBWidget, - bootstrap, - $, - Promise, - Config, - AppCard, - DisplayUtil, - catalog_client_api, - kbase_client_api, - kbaseAuthenticatedWidget + KBWidget, + bootstrap, + $, + Promise, + Config, + AppCard, + DisplayUtil, + catalog_client_api, + kbase_client_api, + kbaseAuthenticatedWidget, + Jupyter ) { 'use strict'; return KBWidget({ @@ -218,21 +220,8 @@ define ([ // Used to trigger clickCallback: function(appCard) { - var self = this; - self.trigger('hideSidePanelOverlay.Narrative'); - if(appCard.type === 'method') { - self.nms.get_method_spec({ids:[appCard.info.id],tag:self.options.tag}) - .then(function(spec){ - // todo: cache this sped into the methods list - self.trigger('methodClicked.Narrative', [spec[0], self.options.tag]); - }); - } else if (appCard.type === 'app'){ - self.nms.get_app_spec({ids:[appCard.info.id],tag:self.options.tag}) - .then(function(spec){ - // todo: cache this sped into the methods list - self.trigger('appClicked.Narrative', [spec[0], self.options.tag]); - }); - } + this.trigger('hideSidePanelOverlay.Narrative'); + Jupyter.narrative.addAndPopulateApp(appCard.info.id, this.options.tag); }, renderControlToolbar: function () { diff --git a/kbase-extension/static/kbase/js/widgets/narrative_core/kbaseAppCard.js b/kbase-extension/static/kbase/js/widgets/narrative_core/kbaseAppCard.js new file mode 100644 index 0000000000..416205137f --- /dev/null +++ b/kbase-extension/static/kbase/js/widgets/narrative_core/kbaseAppCard.js @@ -0,0 +1,157 @@ +/** + * kbaseAppCard.js -- used making App cards in narrative + * + * Authors: zzheng@lbl.gov + * + * Example and expected out put: + * + * var $card = new kbaseAppCard( + { + //expected values + app: app (object with info object and favorite field), + favorite: app.favorite (number), + + //values with default, passing in value will override default + createdBy: array of strs, + type: str, + + //optional values + moreContent: jquery object, + }); + * + * + app.info: + authors:["rsutormin"], categories:["annotation"], icon:{url:}, id: str, + input_types: ['KbaseGenomeAnnotations.Assembly'], module_name: str, + name: str, namespace: str, output_types:["KBaseGenomes.Genome"], + subtitle: str, tooltip: str, ver: str + + +*/ + +define ( + [ + 'bootstrap', + 'util/icon', + 'bluebird', + 'util/bootstrapDialog', + 'util/display', + 'kbase/js/widgets/narrative_core/kbaseCardLayout', + 'narrativeConfig', + 'jquery' + ], function( + bootstrap, + Icon, + Promise, + BootstrapDialog, + DisplayUtil, + kbaseCardLayout, + Config, + $ + ) { + function KbaseAppCard(entry) { + var self = this; + var favorite = entry.app.favorite; + var app = entry.app.info; + + var shortName = entry.name ? entry.name : app.name; + var authors = entry.createdBy; + var version = entry.version ? entry.version : ('v' + app.ver); + var type; + + if(entry.createdBy === undefined){ + if(app.authors.length >2){ + authors = app.authors.slice(0,2).join(', ') + '+ ' + (app.authors.length-2) + ' more'; + }else{ + authors = app.authors.join(', '); + } + } + if (app.module_name && (entry.version === undefined)) { + type = '' + + app.namespace + ' '; + } + + var $star = $('').addClass('fa fa-star kbcb-star-default'); + if (favorite) { + $star.addClass('fa fa-star kbcb-star-favorite').append(' '); + } + + $star.click(function(e){ + e.stopPropagation(); + var params = {}; + if (app.module_name) { + params['module_name'] = app.module_name; + params['id'] = app.id.split('/')[1]; + } else { + params['id'] = app.id; + } + + if (favorite) { + Promise.resolve(self.catalog.remove_favorite(params)) + .then(function () { + $star.removeClass('kbcb-star-favorite'); + favorite = null; // important to set this if we don't refresh the panel + }) + .catch(function (error) { + console.error(error); + }); + } else { + Promise.resolve(self.catalog.add_favorite(params)) + .then(function () { + $star.addClass('kbcb-star-favorite'); + favorite = new Date().getTime(); // important to set this if we don't refresh the panel + }) + .catch(function (error) { + console.error(error); + }); + } + }) + .tooltip({ + title: 'Add or remove from your favorites', + container: 'body', + placement: 'bottom', + delay: { + show: Config.get('tooltip').showDelay, + hide: Config.get('tooltip').hideDelay + } + }); + + var $logo = $('
'); + if (app.icon && app.icon.url) { + var url = Config.url('narrative_method_store_image')+ app.icon.url; + $logo.append(DisplayUtil.getAppIcon({ url: url, cursor: 'pointer', setColor: true, size: '50px' })); + } else { + $logo.append(DisplayUtil.getAppIcon({ cursor: 'pointer', setColor: true })); + } + + var $name = $('
').addClass('kb-data-list-name').append(shortName); + var $version = $('').addClass('kb-data-list-version').append(version); + var $type = $('').addClass('kb-data-list-type').append(type).append($version); + var $date = $('').addClass('kb-data-list-date'); + var $authors = $('').addClass('kb-data-list-edit-by').append(authors); + + var $title = $('
').append($name).append($star); + + var $subcontent = $('
') + .addClass('narrative-data-list-subcontent') + .append($('').append($star)) + .append($type) + .append($date); + + if (entry.createdBy === undefined || entry.createdBy) { + $subcontent.append($authors); + } + + var layout = { + logo: $logo, + title: $title, + subcontent: $subcontent, + moreContent : entry.moreContent + }; + + var $card = new kbaseCardLayout(layout); + + return $card; + } + return KbaseAppCard; //end init + }); diff --git a/kbase-extension/static/kbase/js/widgets/narrative_core/kbaseCardLayout.js b/kbase-extension/static/kbase/js/widgets/narrative_core/kbaseCardLayout.js new file mode 100644 index 0000000000..23adfbcdd6 --- /dev/null +++ b/kbase-extension/static/kbase/js/widgets/narrative_core/kbaseCardLayout.js @@ -0,0 +1,95 @@ +/** + * kbaseCard.js -- used for creating narrative cards layouts + * + * Authors: zzheng@lbl.gov + * + * This the templating function for creating consistent card. Should be called through card + * creating function such as kbaseDataCard. + * + * Expected options: + * options : { + * logo: jqeuery object containing stylized logo, + * actionButtonText: jquery obejct of content to be shown on button, + * actionButtonClick :callback attached to actionButton, + * title: jquery object containing text to be shown on title line, + * subcontent: jquery object containing text to be below title line, + * moreContent jquery object shown when expanding card + * } +*/ + +define ( + [ + 'bootstrap', + 'jquery' + ], function( + bootstrap, + $ + ) { + function KbaseCardLayout(options) { + + //partitions + var $card = $('
').addClass('narrative-card-row'); + var $mainContent = $('
').addClass('narrative-card-row-main'); + var $moreContent = $('
').addClass('narrative-card-row-more').hide(); + var $info = $('
').addClass('kb-data-list-info'); + + var $toggleAdvancedViewBtn =$('
'); + + //if have sub content, add toggle + if(options.moreContent) { + $moreContent.append(options.moreContent); + $toggleAdvancedViewBtn + .hide() + .html($('