2 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/6a8bb831b771/ Changeset: 6a8bb831b771 User: carlfeberhard Date: 2013-08-06 21:02:10 Summary: Visualizations framework: PluginFramework class, serving static and template files from plugins Affected #: 19 files diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 .hgignore --- a/.hgignore +++ b/.hgignore @@ -60,7 +60,7 @@ job_conf.xml data_manager_conf.xml shed_data_manager_conf.xml -config/visualizations/*.xml +config/* static/welcome.html.* static/welcome.html diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/plugins/visualizations/README.txt --- /dev/null +++ b/config/plugins/visualizations/README.txt @@ -0,0 +1,34 @@ +Custom visualization plugins +---------------------------- + +Visualizations can be added to your Galaxy instance by creating +sub-directories, templates, and static files here. + +Properly configured and written visualizations will be accessible to +the user when they click the 'visualizations' icon for a dataset +in their history panel. + +The framework must be enabled in your 'universe_wsgi.ini' file by +uncommenting (and having a valid path for) the +'visualizations_plugin_directory' entry. + +For more information, see http://wiki.galaxyproject.org/VisualizationsRegistry + + +Sub-directory structure +----------------------- + +In general, sub-directories should follow the pattern: + + my_visualization/ + config/ + my_visualization.xml + static/ + ... any static files the visualization needs (if any) + templates/ + ... any Mako templates the visualization needs + +The XML config file for a visualization plugin can be validated on the command +line using (from your plugin directory): + + xmllint my_visualization/config/my_visualization.xml --valid --noout diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/plugins/visualizations/visualization.dtd --- /dev/null +++ b/config/plugins/visualizations/visualization.dtd @@ -0,0 +1,130 @@ +<!-- each visualization must have a template (all other elements are optional) --> +<!ELEMENT visualization (data_sources*,params*,template_root*,template,link_text*,render_location*)> +<!-- visualization name (e.g. 'trackster', 'scatterplot', etc.) is required --> +<!ATTLIST visualization + name CDATA #REQUIRED +> + +<!ELEMENT data_sources (data_source*)> +<!-- data sources are elements that describe what objects (HDAs, LDDAs, Job, User, etc.) + are applicable to a visualization. Often these are used to fetch applicable links + to the visualizations that use them. +--> + <!ELEMENT data_source (model_class,(test|to_param)*)> + <!ELEMENT model_class (#PCDATA)> + <!-- model_class is currently the class name of the object you want to make a visualization + applicable to (e.g. HistoryDatasetAssociation). Currently only classes in galaxy.model + can be used. + REQUIRED and currently limited to: 'HistoryDatasetAssociation', 'LibraryDatasetDatasetAssociation' + --> + <!ELEMENT test (#PCDATA)> + <!-- tests help define what conditions the visualization can be applied to the model_class/target. + Currently, all tests are OR'd and there is no logical grouping. Tests are run in order. + (text): the text of this element is what the given target will be compared to (REQUIRED) + type: what type of test to run (e.g. when the target is an HDA the test will often be of type 'isinstance' + and test whether the HDA's datatype isinstace of a class) + DEFAULT: string comparison. + test_attr: what attribute of the target object should be used in the test. For instance, 'datatype' + will attempt to get the HDA.datatype from a target HDA. If the given object doesn't have + that attribute the test will fail (with no error). test_attr can be dot separated attributes, + looking up each in turn. For example, if the target was a history, one could access the + history.user.email by setting test_attr to 'user.email' (why you would want that, I don't know) + DEFAULT: to comparing the object itself (and not any of it's attributes) + result_type: if the result (the text of the element mentioned above) needs to be parsed into + something other than a string, result_type will tell the registry how to do this. E.g. + if result_type is 'datatype' the registry will assume the text is a datatype class name + and parse it into the proper class before the test (often 'isinstance') is run. + DEFAULT: no parsing (result should be a string) + --> + <!ATTLIST test + type CDATA #IMPLIED + test_attr CDATA #IMPLIED + result_type CDATA #IMPLIED + > + + <!ELEMENT to_param (#PCDATA)> + <!-- to_param tells the registry how to parse the data_source into a query string param. + For example, HDA data_sources can set param_to text to 'dataset_id' and param_attr to 'id' and the + the target HDA (if it passes the tests) will be passed as "dataset_id=HDA.id" + (text): the query string param key this source will be parsed into (e.g. dataset_id) + REQUIRED + param_attr: the attribute of the data_source object to use as the value in the query string param. + E.g. param_attr='id' for an HDA data_source would use the (encoded) id. + NOTE: a to_param MUST have either a param_attr or assign + assign: you can use this to directly assign a value to a query string's param. E.g. if the + data_source is a LDDA we can set 'hda_or_ldda=ldda' using assign='ldda'. + NOTE: a to_param MUST have either a param_attr or assign + --> + <!ATTLIST to_param + param_attr CDATA #IMPLIED + assign CDATA #IMPLIED + > + +<!ELEMENT params ((param|param_modifier)*)> +<!-- params describe what data will be sent to a visualization template and + how to convert them from a query string in a URL into variables usable in a template. + For example, + param_modifiers are a special class of parameters that modify other params + (e.g. hda_ldda can be 'hda' or 'ldda' and modifies/informs dataset_id to fetch an HDA or LDDA) +--> + <!ELEMENT param (#PCDATA)> + <!-- param tells the registry how to parse the query string param back into a resource/data_source. + For example, if a query string has "dataset_id=NNN" and the type is 'dataset', the registry + will attempt to fetch the hda with id of NNN from the database and pass it to the template. + (text): the query string param key this source will be parsed from (e.g. dataset_id) + REQUIRED + type: the type of the resource. + Can be: str (DEFAULT), bool, int, float, json, visualization, dbkey, dataset, or hda_ldda. + default: if a param is not passed on the query string (and is not required) OR the given param + fails to parse, this value is used instead. + DEFAULT: None + required: set this to true if the param is required for the template. Rendering will with an error + if the param hasn't been sent. + DEFAULT: false + csv: set this to true if the param is a comma separated list. The registry will attempt to + parse each value as the given type and send the result as a list to the template. + DEFAULT: false + constrain_to: (currently unused) constain a param to a set of values, error if not valid. + DEFAULT: don't constrain + var_name_in_template: a new name for the resource/variable to use in the template. E.g. an initial + query string param key might be 'dataset_id' in the URL, the registry parses it into an HDA, + and if var_name_in_template is set to 'hda', the template will be able to access the HDA + with the variable name 'hda' (as in hda.title). + DEFAULT: keep the original query string name + --> + <!ATTLIST param + type CDATA #IMPLIED + default CDATA #IMPLIED + required CDATA #IMPLIED + csv CDATA #IMPLIED + constrain_to CDATA #IMPLIED + var_name_in_template CDATA #IMPLIED + > + <!-- param_modifiers are the same as param but have a REQUIRED 'modifies' attribute. + 'modifies' must point to the param name (the text part of param element) that it will modify. + E.g. <param_modifier modifies="dataset_id">hda_ldda</param_modifier> + --> + <!ELEMENT param_modifier (#PCDATA)> + <!ATTLIST param_modifier + modifies CDATA #REQUIRED + type CDATA #IMPLIED + default CDATA #IMPLIED + required CDATA #IMPLIED + csv CDATA #IMPLIED + constrain_to CDATA #IMPLIED + var_name_in_template CDATA #IMPLIED + > + +<!-- template_root: the directory to search for the template relative to templates/webapps/galaxy + (optional) DEFAULT: visualizations +--> +<!ELEMENT template_root (#PCDATA)> +<!-- template: the template used to render the visualization. REQUIRED --> +<!ELEMENT template (#PCDATA)> +<!-- link_text: the text component of an html anchor displayed when the registry builds the link information --> +<!ELEMENT link_text (#PCDATA)> +<!-- render_location: used as the target attribute of the link to the visualization. + Can be 'galaxy_main', '_top', '_blank'. DEFAULT: 'galaxy_main' +--> +<!-- TODO: rename -> render_target --> +<!ELEMENT render_location (#PCDATA)> diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/visualizations/circster.xml.sample --- a/config/visualizations/circster.xml.sample +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE visualization SYSTEM "visualization.dtd"> -<visualization name="circster"> - <data_sources> - <data_source> - <model_class>HistoryDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test> - <to_param param_attr="id">dataset_id</to_param> - <to_param assign="hda">hda_ldda</to_param> - </data_source> - <data_source> - <model_class>LibraryDatasetDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test> - <to_param param_attr="id">dataset_id</to_param> - <to_param assign="ldda">hda_ldda</to_param> - </data_source> - </data_sources> - <params> - <param type="visualization">id</param> - <param type="hda_or_ldda">dataset_id</param> - <param_modifier type="string" modifies="dataset_id">hda_ldda</param_modifier> - <param type="dbkey">dbkey</param> - </params> - <template>circster.mako</template> - <render_location>_top</render_location> -</visualization> diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/visualizations/phyloviz.xml.sample --- a/config/visualizations/phyloviz.xml.sample +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE visualization SYSTEM "visualization.dtd"> -<visualization name="phyloviz"> - <data_sources> - <data_source> - <model_class>HistoryDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Newick</test> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Nexus</test> - <to_param param_attr="id">dataset_id</to_param> - </data_source> - </data_sources> - <params> - <param type="dataset" var_name_in_template="hda" required="true">dataset_id</param> - <param type="integer" default="0">tree_index</param> - </params> - <template>phyloviz.mako</template> - <render_location>_top</render_location> -</visualization> diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/visualizations/scatterplot.xml.sample --- a/config/visualizations/scatterplot.xml.sample +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE visualization SYSTEM "visualization.dtd"> -<visualization name="scatterplot"> - <data_sources> - <data_source> - <model_class>HistoryDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">tabular.Tabular</test> - <to_param param_attr="id">dataset_id</to_param> - </data_source> - </data_sources> - <params> - <param type="dataset" var_name_in_template="hda" required="true">dataset_id</param> - </params> - <template>scatterplot.mako</template> -</visualization> diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/visualizations/sweepster.xml.sample --- a/config/visualizations/sweepster.xml.sample +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE visualization SYSTEM "visualization.dtd"> -<visualization name="sweepster"> - <data_sources> - <data_source> - <model_class>HistoryDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test> - <to_param param_attr="id">dataset_id</to_param> - <to_param assign="hda">hda_ldda</to_param> - </data_source> - <data_source> - <model_class>LibraryDatasetDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test> - <to_param param_attr="id">dataset_id</to_param> - <to_param assign="ldda">hda_ldda</to_param> - </data_source> - </data_sources> - <params> - <param type="visualization" var_name_in_template="viz">visualization</param> - <param type="hda_or_ldda" var_name_in_template="dataset">dataset_id</param> - <param_modifier type="string" modifies="dataset_id">hda_ldda</param_modifier> - </params> - <template>sweepster.mako</template> - <render_location>_top</render_location> -</visualization> diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/visualizations/trackster.xml.sample --- a/config/visualizations/trackster.xml.sample +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE visualization SYSTEM "visualization.dtd"> -<visualization name="trackster"> - <!--not tested yet --> - <data_sources> - <data_source> - <model_class>HistoryDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test> - <to_param param_attr="id">dataset_id</to_param> - <to_param assign="hda">hda_ldda</to_param> - <to_param param_attr="dbkey">dbkey</to_param> - </data_source> - <data_source> - <model_class>LibraryDatasetDatasetAssociation</model_class> - <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test> - <to_param param_attr="id">dataset_id</to_param> - <to_param assign="ldda">hda_ldda</to_param> - </data_source> - </data_sources> - <params> - <param type="visualization">id</param> - <param type="dataset">dataset_id</param> - <param type="genome_region">genome_region</param> - <param type="dbkey">dbkey</param> - </params> - <template_root>tracks</template_root> - <template>browser.mako</template> - <render_location>_top</render_location> -</visualization> diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 config/visualizations/visualization.dtd --- a/config/visualizations/visualization.dtd +++ /dev/null @@ -1,132 +0,0 @@ -<!-- runnable on NIX with xmllint --> - -<!-- each visualization must have a template (all other elements are optional) --> -<!ELEMENT visualization (data_sources*,params*,template_root*,template,link_text*,render_location*)> -<!-- visualization name (e.g. 'trackster', 'scatterplot', etc.) is required --> -<!ATTLIST visualization - name CDATA #REQUIRED -> - -<!ELEMENT data_sources (data_source*)> -<!-- data sources are elements that describe what objects (HDAs, LDDAs, Job, User, etc.) - are applicable to a visualization. Often these are used to fetch applicable links - to the visualizations that use them. ---> - <!ELEMENT data_source (model_class,(test|to_param)*)> - <!ELEMENT model_class (#PCDATA)> - <!-- model_class is currently the class name of the object you want to make a visualization - applicable to (e.g. HistoryDatasetAssociation). Currently only classes in galaxy.model - can be used. - REQUIRED and currently limited to: 'HistoryDatasetAssociation', 'LibraryDatasetDatasetAssociation' - --> - <!ELEMENT test (#PCDATA)> - <!-- tests help define what conditions the visualization can be applied to the model_class/target. - Currently, all tests are OR'd and there is no logical grouping. Tests are run in order. - (text): the text of this element is what the given target will be compared to (REQUIRED) - type: what type of test to run (e.g. when the target is an HDA the test will often be of type 'isinstance' - and test whether the HDA's datatype isinstace of a class) - DEFAULT: string comparison. - test_attr: what attribute of the target object should be used in the test. For instance, 'datatype' - will attempt to get the HDA.datatype from a target HDA. If the given object doesn't have - that attribute the test will fail (with no error). test_attr can be dot separated attributes, - looking up each in turn. For example, if the target was a history, one could access the - history.user.email by setting test_attr to 'user.email' (why you would want that, I don't know) - DEFAULT: to comparing the object itself (and not any of it's attributes) - result_type: if the result (the text of the element mentioned above) needs to be parsed into - something other than a string, result_type will tell the registry how to do this. E.g. - if result_type is 'datatype' the registry will assume the text is a datatype class name - and parse it into the proper class before the test (often 'isinstance') is run. - DEFAULT: no parsing (result should be a string) - --> - <!ATTLIST test - type CDATA #IMPLIED - test_attr CDATA #IMPLIED - result_type CDATA #IMPLIED - > - - <!ELEMENT to_param (#PCDATA)> - <!-- to_param tells the registry how to parse the data_source into a query string param. - For example, HDA data_sources can set param_to text to 'dataset_id' and param_attr to 'id' and the - the target HDA (if it passes the tests) will be passed as "dataset_id=HDA.id" - (text): the query string param key this source will be parsed into (e.g. dataset_id) - REQUIRED - param_attr: the attribute of the data_source object to use as the value in the query string param. - E.g. param_attr='id' for an HDA data_source would use the (encoded) id. - NOTE: a to_param MUST have either a param_attr or assign - assign: you can use this to directly assign a value to a query string's param. E.g. if the - data_source is a LDDA we can set 'hda_or_ldda=ldda' using assign='ldda'. - NOTE: a to_param MUST have either a param_attr or assign - --> - <!ATTLIST to_param - param_attr CDATA #IMPLIED - assign CDATA #IMPLIED - > - -<!ELEMENT params ((param|param_modifier)*)> -<!-- params describe what data will be sent to a visualization template and - how to convert them from a query string in a URL into variables usable in a template. - For example, - param_modifiers are a special class of parameters that modify other params - (e.g. hda_ldda can be 'hda' or 'ldda' and modifies/informs dataset_id to fetch an HDA or LDDA) ---> - <!ELEMENT param (#PCDATA)> - <!-- param tells the registry how to parse the query string param back into a resource/data_source. - For example, if a query string has "dataset_id=NNN" and the type is 'dataset', the registry - will attempt to fetch the hda with id of NNN from the database and pass it to the template. - (text): the query string param key this source will be parsed from (e.g. dataset_id) - REQUIRED - type: the type of the resource. - Can be: str (DEFAULT), bool, int, float, json, visualization, dbkey, dataset, or hda_ldda. - default: if a param is not passed on the query string (and is not required) OR the given param - fails to parse, this value is used instead. - DEFAULT: None - required: set this to true if the param is required for the template. Rendering will with an error - if the param hasn't been sent. - DEFAULT: false - csv: set this to true if the param is a comma separated list. The registry will attempt to - parse each value as the given type and send the result as a list to the template. - DEFAULT: false - constrain_to: (currently unused) constain a param to a set of values, error if not valid. - DEFAULT: don't constrain - var_name_in_template: a new name for the resource/variable to use in the template. E.g. an initial - query string param key might be 'dataset_id' in the URL, the registry parses it into an HDA, - and if var_name_in_template is set to 'hda', the template will be able to access the HDA - with the variable name 'hda' (as in hda.title). - DEFAULT: keep the original query string name - --> - <!ATTLIST param - type CDATA #IMPLIED - default CDATA #IMPLIED - required CDATA #IMPLIED - csv CDATA #IMPLIED - constrain_to CDATA #IMPLIED - var_name_in_template CDATA #IMPLIED - > - <!-- param_modifiers are the same as param but have a REQUIRED 'modifies' attribute. - 'modifies' must point to the param name (the text part of param element) that it will modify. - E.g. <param_modifier modifies="dataset_id">hda_ldda</param_modifier> - --> - <!ELEMENT param_modifier (#PCDATA)> - <!ATTLIST param_modifier - modifies CDATA #REQUIRED - type CDATA #IMPLIED - default CDATA #IMPLIED - required CDATA #IMPLIED - csv CDATA #IMPLIED - constrain_to CDATA #IMPLIED - var_name_in_template CDATA #IMPLIED - > - -<!-- template_root: the directory to search for the template relative to templates/webapps/galaxy - (optional) DEFAULT: visualizations ---> -<!ELEMENT template_root (#PCDATA)> -<!-- template: the template used to render the visualization. REQUIRED --> -<!ELEMENT template (#PCDATA)> -<!-- link_text: the text component of an html anchor displayed when the registry builds the link information --> -<!ELEMENT link_text (#PCDATA)> -<!-- render_location: used as the target attribute of the link to the visualization. - Can be 'galaxy_main', '_top', '_blank'. DEFAULT: 'galaxy_main' ---> -<!-- TODO: rename -> render_target --> -<!ELEMENT render_location (#PCDATA)> diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/app.py --- a/lib/galaxy/app.py +++ b/lib/galaxy/app.py @@ -123,10 +123,8 @@ # Load genome indexer tool. load_genome_index_tools( self.toolbox ) # visualizations registry: associates resources with visualizations, controls how to render - self.visualizations_registry = None - if self.config.visualizations_config_directory: - self.visualizations_registry = VisualizationsRegistry( self.config.root, - self.config.visualizations_config_directory ) + self.visualizations_registry = VisualizationsRegistry.from_config( + self.config.visualizations_plugins_directory, self.config ) # Load security policy. self.security_agent = self.model.security_agent self.host_security_agent = galaxy.security.HostAgent( model=self.security_agent.model, permitted_actions=self.security_agent.permitted_actions ) diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/config.py --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -291,8 +291,10 @@ self.fluent_log = string_as_bool( kwargs.get( 'fluent_log', False ) ) self.fluent_host = kwargs.get( 'fluent_host', 'localhost' ) self.fluent_port = int( kwargs.get( 'fluent_port', 24224 ) ) - # visualization registries config directory - self.visualizations_config_directory = kwargs.get( 'visualizations_config_directory', None ) + # PLUGINS: + self.plugin_frameworks = [] + # visualization framework + self.visualizations_plugins_directory = kwargs.get( 'visualizations_plugins_directory', None ) @property def sentry_dsn_public( self ): diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/visualization/registry.py --- a/lib/galaxy/visualization/registry.py +++ b/lib/galaxy/visualization/registry.py @@ -12,6 +12,8 @@ import galaxy.model from galaxy.web import url_for +from galaxy.web.base import pluginframework + import logging log = logging.getLogger( __name__ ) @@ -28,17 +30,16 @@ tests: anding, grouping, not has_dataprovider + user is admin data_sources: lists of add description element to visualization. -TESTS to add: - has dataprovider - user is admin +user_pref for ordering/ex/inclusion of particular visualizations """ # ------------------------------------------------------------------- the registry -class VisualizationsRegistry( object ): +class VisualizationsRegistry( pluginframework.PluginFramework ): """ Main responsibilities are: - testing if an object has a visualization that can be applied to it @@ -47,6 +48,7 @@ - validating and parsing params into resources (based on a context) used in the visualization template """ + #: any built in visualizations that have their own render method in ctrls/visualization # these should be handled somewhat differently - and be passed onto their resp. methods in ctrl.visualization #TODO: change/remove if/when they can be updated to use this system BUILT_IN_VISUALIZATIONS = [ @@ -55,57 +57,37 @@ 'sweepster', 'phyloviz' ] - # where to search for visualiztion templates (relative to templates/webapps/galaxy) + #: where to search for visualiztion templates (relative to templates/webapps/galaxy) # this can be overridden individually in the config entries TEMPLATE_ROOT = 'visualization' + #: directories under plugin_directory that aren't plugins + non_plugin_directories = [ 'bler' ] def __str__( self ): - listings_keys_str = ','.join( self.listings.keys() ) if self.listings else '' - return 'VisualizationsRegistry(%s)' %( listings_keys_str ) + return 'VisualizationsRegistry(%s)' %( self.plugin_directory ) - def __init__( self, galaxy_root, configuration_filepath ): - # load the registry from the xml files located in configuration_filepath using the given parser - configuration_filepath = os.path.join( galaxy_root, configuration_filepath ) - self.configuration_filepath = self.check_conf_filepath( configuration_filepath ) - self.move_sample_conf_files() - self.load() + def __init__( self, registry_filepath, template_cache_dir ): + super( VisualizationsRegistry, self ).__init__( registry_filepath, template_cache_dir ) # what to use to parse query strings into resources/vars for the template self.resource_parser = ResourceParser() + log.debug( '%s loaded', str( self ) ) - def check_conf_filepath( self, configuration_filepath ): + def load_configuration( self ): """ - Checks for the existence of the given filepath. - :param configurarion_filepath: full filepath to the visualization config directory - :raises IOError: if the given directory doesn't exist + Builds the registry by parsing the `config/*.xml` files for every plugin + in ``get_plugin_directories`` and stores the results in ``self.listings``. + + ..note:: + This could be used to re-load a new configuration without restarting + the instance. """ - if not os.path.exists( configuration_filepath ): - raise IOError( 'visualization configuration directory (%s) not found' %( configuration_filepath ) ) - return configuration_filepath + try: + self.listings = VisualizationsConfigParser.parse( self.get_plugin_directories() ) - def move_sample_conf_files( self ): - """ - Copies any `*.xml.sample` files in `configuration_filepath` to - `.xml` files of the same names if no file with that name already exists. - - :returns: a list of the files moved - """ - files_moved = [] - for sample_file in glob.glob( os.path.join( self.configuration_filepath, '*.sample' ) ): - new_name = os.path.splitext( sample_file )[0] - if not os.path.exists( new_name ): - shutil.copy2( sample_file, new_name ) - files_moved.append( new_name ) - - def load( self ): - """ - Builds the registry by parsing the xml in `self.configuration_filepath` - and stores the results in `self.listings`. - - Provided as separate method from `__init__` in order to re-load a - new configuration without restarting the instance. - """ - self.listings = VisualizationsConfigParser.parse( self.configuration_filepath ) + except Exception, exc: + log.exception( 'Error parsing visualizations plugins %s', self.plugin_directory ) + raise def get_visualization( self, trans, visualization_name, target_object ): """ @@ -283,11 +265,11 @@ VALID_RENDER_LOCATIONS = [ 'galaxy_main', '_top', '_blank' ] @classmethod - def parse( cls, config_dir, debug=True ): + def parse( cls, plugin_directories, debug=False ): """ Static class interface. """ - return cls( debug ).parse_files( config_dir ) + return cls( debug ).parse_plugins( plugin_directories ) def __init__( self, debug=False ): self.debug = debug @@ -297,33 +279,45 @@ self.param_parser = ParamParser() self.param_modifier_parser = ParamModifierParser() - def parse_files( self, config_dir ): + def parse_plugins( self, plugin_directories ): """ - Parse each XML file in `config_dir` for visualizations config data. + Parses the config files for each plugin sub-dir in `base_path`. + + :param plugin_directories: a list of paths to enabled plugins. + + :returns: registry data in dictionary form + """ + returned = {} + for plugin_path in plugin_directories: + returned.update( self.parse_plugin( plugin_path ) ) + return returned + + def parse_plugin( self, plugin_path ): + """ + Parses any XML files in ``<plugin_path>/config``. If an error occurs while parsing a visualizations entry, it is skipped. :returns: registry data in dictionary form + ..note:: + assumes config files are in a 'config' sub-dir of each plugin """ returned = {} - try: - for xml_filepath in glob.glob( os.path.join( config_dir, '*.xml' ) ): - try: - visualization_name, visualization = self.parse_file( xml_filepath ) - # skip vis' with parsing errors - don't shutdown the startup - except ParsingException, parse_exc: - log.error( 'Skipped visualization config "%s" due to parsing errors: %s', - xml_filepath, str( parse_exc ), exc_info=self.debug ) - if visualization: - returned[ visualization_name ] = visualization - log.debug( 'Visualization config loaded for: %s', visualization_name ) + plugin_config_path = os.path.join( plugin_path, 'config' ) + if not os.path.isdir( plugin_config_path ): + return returned - except Exception, exc: - log.error( 'Error parsing visualizations configuration directory %s: %s', - config_dir, str( exc ), exc_info=( not self.debug ) ) - #TODO: change when this framework is on by default - if self.debug: - raise + for xml_filepath in glob.glob( os.path.join( plugin_config_path, '*.xml' ) ): + try: + visualization_name, visualization = self.parse_file( xml_filepath ) + # skip vis' with parsing errors - don't shutdown the startup + except ParsingException, parse_exc: + log.error( 'Skipped visualization config "%s" due to parsing errors: %s', + xml_filepath, str( parse_exc ), exc_info=self.debug ) + + if visualization: + returned[ visualization_name ] = visualization + log.debug( 'Visualization config loaded for: %s', visualization_name ) return returned diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/web/base/pluginframework.py --- /dev/null +++ b/lib/galaxy/web/base/pluginframework.py @@ -0,0 +1,216 @@ +""" +Base class for plugins - frameworks or systems that may: + * serve static content + * serve templated html + * have some configuration at startup +""" + +import os.path +import glob +import sys + +import pkg_resources +pkg_resources.require( 'MarkupSafe' ) +pkg_resources.require( 'Mako' ) +import mako + + +# ============================================================================= exceptions +class PluginFrameworkException( Exception ): + """Base exception for plugin frameworks. + """ + pass +class PluginFrameworkConfigException( PluginFrameworkException ): + """Exception for plugin framework configuration errors. + """ + pass +class PluginFrameworkStaticException( PluginFrameworkException ): + """Exception for plugin framework static directory set up errors. + """ + pass +class PluginFrameworkTemplateException( PluginFrameworkException ): + """Exception for plugin framework template directory + and template rendering errors. + """ + pass + + +# ============================================================================= base +class PluginFramework( object ): + """ + Plugins are files/directories living outside the Galaxy ``lib`` directory + that serve static files (css, js, images, etc.), use and serve mako templates, + and have some configuration to control the rendering. + + A plugin framework sets up all the above components. + """ + #: does the class need a config file(s) to be parsed? + has_config = True + #: does the class need static files served? + serves_static = True + #: does the class need template files served? + serves_templates = True + #TODO: allow plugin mako inheritance from existing ``/templates`` files + #uses_galaxy_templates = True + #TODO: possibly better as instance var (or a combo) + #: the directories in ``plugin_directory`` with basenames listed here will + #: be ignored for config, static, and templates + non_plugin_directories = [] + + # ------------------------------------------------------------------------- setup + @classmethod + def from_config( cls, config_plugin_directory, config ): + """ + Set up the framework based on data from some config object by: + * constructing it's absolute plugin_directory filepath + * getting a template_cache + * and appending itself to the config object's ``plugin_frameworks`` list + + .. note:: + precondition: config obj should have attributes: + root, template_cache, and (list) plugin_frameworks + """ + # currently called from (base) app.py - defined here to allow override if needed + if not config_plugin_directory: + return None + try: + full_plugin_filepath = os.path.join( config.root, config_plugin_directory ) + template_cache = config.template_cache if cls.serves_static else None + plugin = cls( full_plugin_filepath, template_cache ) + + config.plugin_frameworks.append( plugin ) + return plugin + + except PluginFrameworkException, plugin_exc: + log.exception( "Error loading framework %s. Skipping...", cls.__class__.__name__ ) + return None + + def __str__( self ): + return '%s(%s)' %( self.__class__.__name__, self.plugin_directory ) + + def __init__( self, plugin_directory, template_cache_dir=None, debug=False ): + if not os.path.isdir( plugin_directory ): + raise PluginFrameworkException( 'Framework plugin directory not found: %s, %s' + %( self.__class__.__name__, plugin_directory ) ) + # absolute (?) path + self.plugin_directory = plugin_directory + self.name = os.path.basename( self.plugin_directory ) + + if self.has_config: + self.load_configuration() + # set_up_static_urls will be called during the static middleware creation (if serves_static) + if self.serves_templates: + self.set_up_templates( template_cache_dir ) + + def get_plugin_directories( self ): + """ + Return the plugin directory paths for this plugin. + + Gets any directories within ``plugin_directory`` that are directories + themselves and whose ``basename`` is not in ``plugin_directory``. + """ + # could instead explicitly list on/off in master config file + for plugin_path in glob.glob( os.path.join( self.plugin_directory, '*' ) ): + if not os.path.isdir( plugin_path ): + continue + + if os.path.basename( plugin_path ) in self.non_plugin_directories: + continue + + yield plugin_path + + # ------------------------------------------------------------------------- config + def load_configuration( self ): + """ + Override to load some framework/plugin specifc configuration. + """ + # Abstract method + return True + + # ------------------------------------------------------------------------- serving static files + def get_static_urls_and_paths( self ): + """ + For each plugin, return a 2-tuple where the first element is a url path + to the plugin's static files and the second is a filesystem path to those + same files. + + Meant to be passed to a Static url map. + """ + url_and_paths = [] + # called during the static middleware creation (buildapp.py, wrap_in_static) + + # NOTE: this only searches for static dirs two levels deep (i.e. <plugin_directory>/<plugin-name>/static) + for plugin_path in self.get_plugin_directories(): + # that path is a plugin, search for subdirs named static in THAT dir + plugin_static_path = os.path.join( plugin_path, 'static' ) + if not os.path.isdir( plugin_static_path ): + continue + + # build a url for that static subdir and create a Static urlmap entry for it + plugin_name = os.path.splitext( os.path.basename( plugin_path ) )[0] + plugin_url = self.name + '/' + plugin_name + '/static' + url_and_paths.append( ( plugin_url, plugin_static_path ) ) + + return url_and_paths + + # ------------------------------------------------------------------------- templates + def set_up_templates( self, template_cache_dir ): + """ + Add a ``template_lookup`` attribute to the framework that can be passed + to the mako renderer to find templates. + """ + if not template_cache_dir: + raise PluginFrameworkTemplateException( 'Plugins that serve templates require a template_cache_dir' ) + self.template_lookup = self._create_mako_template_lookup( template_cache_dir, self._get_template_paths() ) + return self.template_lookup + + def _get_template_paths( self ): + """ + Get the paths that will be searched for templates. + """ + return [ self.plugin_directory ] + + def _create_mako_template_lookup( self, cache_dir, paths, collection_size=500, output_encoding='utf-8' ): + """ + Create a ``TemplateLookup`` with defaults. + """ + return mako.lookup.TemplateLookup( + directories = paths, + module_directory = cache_dir, + collection_size = collection_size, + output_encoding = output_encoding ) + + #TODO: do we want to remove trans and app from the plugin template context? + def fill_template( self, trans, template_filename, **kwargs ): + """ + Pass control over to trans and render the ``template_filename``. + """ + # defined here to be overridden + return trans.fill_template( template_filename, template_lookup=self.template_lookup, **kwargs ) + + def fill_template_with_plugin_imports( self, trans, template_filename, **kwargs ): + """ + Returns a rendered plugin template but allows importing modules from inside + the plugin directory within the template. + + ..example:: I.e. given this layout for a plugin: + bler/ + template/ + bler.mako + static/ + conifg/ + my_script.py + this version of `fill_template` allows `bler.mako` to call `import my_script`. + """ + try: + plugin_base_path = os.path.split( os.path.dirname( template_filename ) )[0] + plugin_path = os.path.join( self.plugin_directory, plugin_base_path ) + sys.path.append( plugin_path ) + filled_template = self.fill_template( trans, template_filename, **kwargs ) + + finally: + sys.path.remove( plugin_path ) + + return filled_template + + #TODO: could add plugin template helpers here diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/web/framework/__init__.py --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -993,10 +993,13 @@ searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] ) return str( template ) - def fill_template_mako( self, filename, **kwargs ): - template = self.webapp.mako_template_lookup.get_template( filename ) + def fill_template_mako( self, filename, template_lookup=None, **kwargs ): + template_lookup = template_lookup or self.webapp.mako_template_lookup + template = template_lookup.get_template( filename ) template.output_encoding = 'utf-8' - data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app ) + + data = dict( caller=self, t=self, trans=self, h=helpers, util=util, + request=self.request, response=self.response, app=self.app ) data.update( self.template_context ) data.update( kwargs ) return template.render( **data ) diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/web/framework/middleware/static.py --- a/lib/galaxy/web/framework/middleware/static.py +++ b/lib/galaxy/web/framework/middleware/static.py @@ -12,9 +12,11 @@ from paste.urlparser import StaticURLParser class CacheableStaticURLParser( StaticURLParser ): + def __init__( self, directory, cache_seconds=None ): StaticURLParser.__init__( self, directory ) self.cache_seconds = cache_seconds + def __call__( self, environ, start_response ): path_info = environ.get('PATH_INFO', '') if not path_info: diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/webapps/galaxy/buildapp.py --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -2,22 +2,26 @@ Provides factory methods to assemble the Galaxy web application """ -import logging, atexit -import os, os.path -import sys, warnings - -from galaxy.util import asbool +import sys +import os +import os.path +import atexit +import warnings +import glob from paste import httpexceptions import pkg_resources -log = logging.getLogger( __name__ ) - -from galaxy import util import galaxy.model import galaxy.model.mapping import galaxy.datatypes.registry import galaxy.web.framework +from galaxy import util +from galaxy.util import asbool + +import logging +log = logging.getLogger( __name__ ) + class GalaxyWebApplication( galaxy.web.framework.WebApplication ): pass @@ -181,7 +185,7 @@ if kwargs.get( 'middleware', True ): webapp = wrap_in_middleware( webapp, global_conf, **kwargs ) if asbool( kwargs.get( 'static_enabled', True ) ): - webapp = wrap_in_static( webapp, global_conf, **kwargs ) + webapp = wrap_in_static( webapp, global_conf, plugin_frameworks=app.config.plugin_frameworks, **kwargs ) if asbool(kwargs.get('pack_scripts', False)): pack_scripts() # Close any pooled database connections before forking @@ -323,7 +327,7 @@ log.debug( "Enabling 'Request ID' middleware" ) return app -def wrap_in_static( app, global_conf, **local_conf ): +def wrap_in_static( app, global_conf, plugin_frameworks=None, **local_conf ): from paste.urlmap import URLMap from galaxy.web.framework.middleware.static import CacheableStaticURLParser as Static urlmap = URLMap() @@ -343,6 +347,16 @@ urlmap["/static/style"] = Static( conf.get( "static_style_dir" ), cache_time ) urlmap["/favicon.ico"] = Static( conf.get( "static_favicon_dir" ), cache_time ) urlmap["/robots.txt"] = Static( conf.get( "static_robots_txt", 'static/robots.txt'), cache_time ) + + # wrap any static dirs for plugins + plugin_frameworks = plugin_frameworks or [] + for static_serving_framework in ( framework for framework in plugin_frameworks if framework.serves_static ): + # invert control to each plugin for finding their own static dirs + for plugin_url, plugin_static_path in static_serving_framework.get_static_urls_and_paths(): + plugin_url = '/plugins/' + plugin_url + urlmap[( plugin_url )] = Static( plugin_static_path, cache_time ) + log.debug( 'added url, path to static middleware: %s, %s', plugin_url, plugin_static_path ) + # URL mapper becomes the root webapp return urlmap diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 lib/galaxy/webapps/galaxy/controllers/visualization.py --- a/lib/galaxy/webapps/galaxy/controllers/visualization.py +++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py @@ -708,7 +708,7 @@ # validate name vs. registry registry = trans.app.visualizations_registry if not registry: - raise HTTPNotFound( 'No visualization registry (possibly disabled in universe_wsgi.ini)') + raise HTTPNotFound( 'No visualization registry (possibly disabled in universe_wsgi.ini)' ) if visualization_name not in registry.listings: raise HTTPNotFound( 'Unknown or invalid visualization: ' + visualization_name ) # or redirect to list? @@ -722,16 +722,15 @@ resources = registry.query_dict_to_resources( trans, self, visualization_name, kwargs ) # look up template and render - template_root = registry_listing.get( 'template_root', registry.TEMPLATE_ROOT ) - template = registry_listing[ 'template' ] - template_path = os.path.join( template_root, template ) + template_path = registry_listing[ 'template' ] + returned = registry.fill_template( trans, template_path, + visualization_name=visualization_name, query_args=kwargs, + embedded=embedded, shared_vars={}, **resources ) #NOTE: passing *unparsed* kwargs as query_args #NOTE: shared_vars is a dictionary for shared data in the template # this feels hacky to me but it's what mako recommends: # http://docs.makotemplates.org/en/latest/runtime.html #TODO: embedded - returned = trans.fill_template( template_path, visualization_name=visualization_name, - embedded=embedded, query_args=kwargs, shared_vars={}, **resources ) except Exception, exception: log.exception( 'error rendering visualization (%s): %s', visualization_name, str( exception ) ) diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 static/scripts/mvc/dataset/hda-edit.js --- a/static/scripts/mvc/dataset/hda-edit.js +++ b/static/scripts/mvc/dataset/hda-edit.js @@ -616,7 +616,7 @@ title : "Scatterplot", type : "url", content : url + '/scatterplot?' + $.param(params), - center : true + location : 'center' }); //TODO: this needs to go away @@ -699,4 +699,4 @@ //============================================================================== //return { // HDAView : HDAView, -//};}); \ No newline at end of file +//};}); diff -r f760fca38915be8d0ae8f94e703a6d83302065f8 -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 universe_wsgi.ini.sample --- a/universe_wsgi.ini.sample +++ b/universe_wsgi.ini.sample @@ -174,10 +174,8 @@ # Galaxy. #datatypes_config_file = datatypes_conf.xml -# Visualizations config directory, where to look for individual visualization -# xml configuration files. Those files define how visualizations apply to -# particular data and how to pass them the necessary parameters -#visualizations_config_directory = config/visualizations +# Visualizations config directory: where to look for individual visualization plugins. +#visualizations_plugins_directory = config/plugins/visualizations # Each job is given a unique empty directory as its current working directory. # This option defines in what parent directory those directories will be https://bitbucket.org/galaxy/galaxy-central/commits/db11479ab758/ Changeset: db11479ab758 User: carlfeberhard Date: 2013-08-06 21:04:05 Summary: Fix centering of scatterplot; pack scripts Affected #: 1 file diff -r 6a8bb831b771b56e79fff3fccfe610d0bf01b885 -r db11479ab7584322519463a33b42f4b911fb1a68 static/scripts/packed/mvc/dataset/hda-edit.js --- a/static/scripts/packed/mvc/dataset/hda-edit.js +++ b/static/scripts/packed/mvc/dataset/hda-edit.js @@ -1,1 +1,1 @@ -var HDAEditView=HDABaseView.extend(LoggableMixin).extend({initialize:function(a){HDABaseView.prototype.initialize.call(this,a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_setUpBehaviors:function(c){HDABaseView.prototype._setUpBehaviors.call(this,c);var a=this,b=this.urls.purge,d=c.find("#historyItemPurger-"+this.model.get("id"));if(d){d.attr("href",["javascript","void(0)"].join(":"));d.click(function(e){var f=jQuery.ajax(b);f.success(function(i,g,h){a.model.set("purged",true);a.trigger("purged",a)});f.error(function(h,g,i){a.trigger("error",_l("Unable to purge this dataset"),h,g,i)})})}},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleButtons:function(){var a=$('<div class="historyItemButtons"></div>');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_editButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false;if(c){b.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(a){b.title=_l("Undelete dataset to edit attributes")}}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var a=this,b=a.urls["delete"],c={title:_l("Delete"),href:b,id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete",on_click:function(){$.ajax({url:b,type:"POST",error:function(){a.$el.show()},success:function(){a.model.set({deleted:true})}})}};if(this.model.get("deleted")||this.model.get("purged")){c={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(c)});return this.deleteButton.render().$el},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(a,{dbkey_unknown_and_editable:true})}return HDABaseView.templates.hdaSummary(a)},_render_errButton:function(){if(this.model.get("state")!==HistoryDatasetAssociation.STATES.ERROR){this.errButton=null;return null}this.errButton=new IconButtonView({model:new IconButton({title:_l("View or report this error"),href:this.urls.report_error,target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_rerunButton:function(){this.rerunButton=new IconButtonView({model:new IconButton({title:_l("Run this job again"),href:this.urls.rerun,target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_visualizationsButton:function(){var a=this.model.get("visualizations");if((!this.model.hasData())||(_.isEmpty(a))){this.visualizationsButton=null;return null}if(_.isObject(a[0])){return this._render_visualizationsFrameworkButton(a)}if(!this.urls.visualization){this.visualizationsButton=null;return null}var c=this.model.get("dbkey"),f=this.urls.visualization,d={},g={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(c){g.dbkey=c}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:this.urls.visualization,icon_class:"chart_curve"})});var b=this.visualizationsButton.render().$el;b.addClass("visualize-icon");function e(h){switch(h){case"trackster":return create_trackster_action_fn(f,g,c);case"scatterplot":return create_scatterplot_action_fn(f,g);default:return function(){parent.frame_manager.frame_new({title:"Visualization",type:"url",content:f+"/"+h+"?"+$.param(g)})}}}if(a.length===1){b.attr("title",a[0]);b.click(e(a[0]))}else{_.each(a,function(i){var h=i.charAt(0).toUpperCase()+i.slice(1);d[_l(h)]=e(i)});make_popupmenu(b,d)}return b},_render_visualizationsFrameworkButton:function(a){if(!(this.model.hasData())||!(a&&!_.isEmpty(a))){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),icon_class:"chart_curve"})});var c=this.visualizationsButton.render().$el;c.addClass("visualize-icon");if(_.keys(a).length===1){c.attr("title",_.keys(a)[0]);c.attr("href",_.values(a)[0])}else{var d=[];_.each(a,function(e){d.push(e)});var b=new PopupMenu(c,d)}return c},_render_secondaryActionButtons:function(b){var c=$("<div/>"),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||(!this.urls.tags.get)){this.tagButton=null;return null}this.tagButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset tags"),target:"galaxy_main",href:this.urls.tags.get,icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!(this.model.hasData())||(!this.urls.annotation.get)){this.annotateButton=null;return null}this.annotateButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset annotation"),target:"galaxy_main",icon_class:"annotate"})});return this.annotateButton.render().$el},_render_tagArea:function(){if(!this.urls.tags.set){return null}return $(HDAEditView.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})).trim())},_render_annotationArea:function(){if(!this.urls.annotation.get){return null}return $(HDAEditView.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})).trim())},_render_body_error:function(a){HDABaseView.prototype._render_body_error.call(this,a);var b=a.find("#primary-actions-"+this.model.get("id"));b.prepend(this._render_errButton())},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('<div class="clear"/>');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayAppArea());this._render_displayApps(a);a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(c){this.log(this+".loadAndDisplayTags",c);var a=this,d=this.$el.find(".tag-area"),b=d.find(".tag-elt");if(d.is(":hidden")){if(!jQuery.trim(b.html())){$.ajax({url:this.urls.tags.get,error:function(g,e,f){a.log("Tagging failed",g,e,f);a.trigger("error",_l("Tagging failed"),g,e,f)},success:function(e){b.html(e);b.find(".tooltip").tooltip();d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.urls.annotation.set;if(d.is(":hidden")){if(!jQuery.trim(c.html())){$.ajax({url:this.urls.annotation.get,error:function(){view.log("Annotation failed",xhr,status,error);view.trigger("error",_l("Annotation failed"),xhr,status,error)},success:function(e){if(e===""){e="<em>"+_l("Describe or add notes to dataset")+"</em>"}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDAView("+a+")"}});HDAEditView.templates={tagArea:Handlebars.templates["template-hda-tagArea"],annotationArea:Handlebars.templates["template-hda-annotationArea"]};function create_scatterplot_action_fn(a,b){action=function(){parent.frame_manager.frame_new({title:"Scatterplot",type:"url",content:a+"/scatterplot?"+$.param(b),center:true});$("div.popmenu-wrapper").remove();return false};return action}function create_trackster_action_fn(a,c,b){return function(){var d={};if(b){d["f-dbkey"]=b}$.ajax({url:a+"/list_tracks?"+$.param(d),dataType:"html",error:function(){alert(_l("Could not add this dataset to browser")+".")},success:function(e){var f=window.parent;f.show_modal(_l("View Data in a New or Saved Visualization"),"",{Cancel:function(){f.hide_modal()},"View in saved visualization":function(){f.show_modal(_l("Add Data to Saved Visualization"),e,{Cancel:function(){f.hide_modal()},"Add to visualization":function(){$(f.document).find("input[name=id]:checked").each(function(){var g=$(this).val();c.id=g;f.hide_modal();f.frame_manager.frame_new({title:"Trackster",type:"url",content:a+"/trackster?"+$.param(c)})})}})},"View in new visualization":function(){f.hide_modal();f.frame_manager.frame_new({title:"Trackster",type:"url",content:a+"/trackster?"+$.param(c)})}})}});return false}}; \ No newline at end of file +var HDAEditView=HDABaseView.extend(LoggableMixin).extend({initialize:function(a){HDABaseView.prototype.initialize.call(this,a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_setUpBehaviors:function(c){HDABaseView.prototype._setUpBehaviors.call(this,c);var a=this,b=this.urls.purge,d=c.find("#historyItemPurger-"+this.model.get("id"));if(d){d.attr("href",["javascript","void(0)"].join(":"));d.click(function(e){var f=jQuery.ajax(b);f.success(function(i,g,h){a.model.set("purged",true);a.trigger("purged",a)});f.error(function(h,g,i){a.trigger("error",_l("Unable to purge this dataset"),h,g,i)})})}},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleButtons:function(){var a=$('<div class="historyItemButtons"></div>');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_editButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false;if(c){b.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(a){b.title=_l("Undelete dataset to edit attributes")}}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var a=this,b=a.urls["delete"],c={title:_l("Delete"),href:b,id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete",on_click:function(){$.ajax({url:b,type:"POST",error:function(){a.$el.show()},success:function(){a.model.set({deleted:true})}})}};if(this.model.get("deleted")||this.model.get("purged")){c={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(c)});return this.deleteButton.render().$el},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(a,{dbkey_unknown_and_editable:true})}return HDABaseView.templates.hdaSummary(a)},_render_errButton:function(){if(this.model.get("state")!==HistoryDatasetAssociation.STATES.ERROR){this.errButton=null;return null}this.errButton=new IconButtonView({model:new IconButton({title:_l("View or report this error"),href:this.urls.report_error,target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_rerunButton:function(){this.rerunButton=new IconButtonView({model:new IconButton({title:_l("Run this job again"),href:this.urls.rerun,target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_visualizationsButton:function(){var a=this.model.get("visualizations");if((!this.model.hasData())||(_.isEmpty(a))){this.visualizationsButton=null;return null}if(_.isObject(a[0])){return this._render_visualizationsFrameworkButton(a)}if(!this.urls.visualization){this.visualizationsButton=null;return null}var c=this.model.get("dbkey"),f=this.urls.visualization,d={},g={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(c){g.dbkey=c}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:this.urls.visualization,icon_class:"chart_curve"})});var b=this.visualizationsButton.render().$el;b.addClass("visualize-icon");function e(h){switch(h){case"trackster":return create_trackster_action_fn(f,g,c);case"scatterplot":return create_scatterplot_action_fn(f,g);default:return function(){parent.frame_manager.frame_new({title:"Visualization",type:"url",content:f+"/"+h+"?"+$.param(g)})}}}if(a.length===1){b.attr("title",a[0]);b.click(e(a[0]))}else{_.each(a,function(i){var h=i.charAt(0).toUpperCase()+i.slice(1);d[_l(h)]=e(i)});make_popupmenu(b,d)}return b},_render_visualizationsFrameworkButton:function(a){if(!(this.model.hasData())||!(a&&!_.isEmpty(a))){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),icon_class:"chart_curve"})});var c=this.visualizationsButton.render().$el;c.addClass("visualize-icon");if(_.keys(a).length===1){c.attr("title",_.keys(a)[0]);c.attr("href",_.values(a)[0])}else{var d=[];_.each(a,function(e){d.push(e)});var b=new PopupMenu(c,d)}return c},_render_secondaryActionButtons:function(b){var c=$("<div/>"),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||(!this.urls.tags.get)){this.tagButton=null;return null}this.tagButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset tags"),target:"galaxy_main",href:this.urls.tags.get,icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!(this.model.hasData())||(!this.urls.annotation.get)){this.annotateButton=null;return null}this.annotateButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset annotation"),target:"galaxy_main",icon_class:"annotate"})});return this.annotateButton.render().$el},_render_tagArea:function(){if(!this.urls.tags.set){return null}return $(HDAEditView.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})).trim())},_render_annotationArea:function(){if(!this.urls.annotation.get){return null}return $(HDAEditView.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})).trim())},_render_body_error:function(a){HDABaseView.prototype._render_body_error.call(this,a);var b=a.find("#primary-actions-"+this.model.get("id"));b.prepend(this._render_errButton())},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('<div class="clear"/>');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayAppArea());this._render_displayApps(a);a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(c){this.log(this+".loadAndDisplayTags",c);var a=this,d=this.$el.find(".tag-area"),b=d.find(".tag-elt");if(d.is(":hidden")){if(!jQuery.trim(b.html())){$.ajax({url:this.urls.tags.get,error:function(g,e,f){a.log("Tagging failed",g,e,f);a.trigger("error",_l("Tagging failed"),g,e,f)},success:function(e){b.html(e);b.find(".tooltip").tooltip();d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.urls.annotation.set;if(d.is(":hidden")){if(!jQuery.trim(c.html())){$.ajax({url:this.urls.annotation.get,error:function(){view.log("Annotation failed",xhr,status,error);view.trigger("error",_l("Annotation failed"),xhr,status,error)},success:function(e){if(e===""){e="<em>"+_l("Describe or add notes to dataset")+"</em>"}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDAView("+a+")"}});HDAEditView.templates={tagArea:Handlebars.templates["template-hda-tagArea"],annotationArea:Handlebars.templates["template-hda-annotationArea"]};function create_scatterplot_action_fn(a,b){action=function(){parent.frame_manager.frame_new({title:"Scatterplot",type:"url",content:a+"/scatterplot?"+$.param(b),location:"center"});$("div.popmenu-wrapper").remove();return false};return action}function create_trackster_action_fn(a,c,b){return function(){var d={};if(b){d["f-dbkey"]=b}$.ajax({url:a+"/list_tracks?"+$.param(d),dataType:"html",error:function(){alert(_l("Could not add this dataset to browser")+".")},success:function(e){var f=window.parent;f.show_modal(_l("View Data in a New or Saved Visualization"),"",{Cancel:function(){f.hide_modal()},"View in saved visualization":function(){f.show_modal(_l("Add Data to Saved Visualization"),e,{Cancel:function(){f.hide_modal()},"Add to visualization":function(){$(f.document).find("input[name=id]:checked").each(function(){var g=$(this).val();c.id=g;f.hide_modal();f.frame_manager.frame_new({title:"Trackster",type:"url",content:a+"/trackster?"+$.param(c)})})}})},"View in new visualization":function(){f.hide_modal();f.frame_manager.frame_new({title:"Trackster",type:"url",content:a+"/trackster?"+$.param(c)})}})}});return false}}; \ No newline at end of file Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.