mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Visualizations API: rough draft of index, show, create, and update; Browser tests: tests for visualizations API
This commit is contained in:
@@ -1,28 +1,263 @@
|
||||
from galaxy import web
|
||||
from galaxy.web.base.controller import BaseController, BaseAPIController
|
||||
"""
|
||||
Visualizations resource control over the API.
|
||||
|
||||
class VisualizationsController( BaseAPIController ):
|
||||
NOTE!: this is a work in progress and functionality and data structures
|
||||
may change often.
|
||||
"""
|
||||
|
||||
import pkg_resources
|
||||
pkg_resources.require( 'SQLAlchemy >= 0.4' )
|
||||
from sqlalchemy import or_
|
||||
|
||||
from galaxy import web, util
|
||||
from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.exceptions import ( ItemAccessibilityException, ItemDeletionException, ItemOwnershipException,
|
||||
MessageException )
|
||||
|
||||
from galaxy.web import url_for
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class VisualizationsController( BaseAPIController, UsesVisualizationMixin, UsesAnnotations ):
|
||||
"""
|
||||
RESTful controller for interactions with visualizations.
|
||||
"""
|
||||
|
||||
@web.expose_api
|
||||
def index( self, trans, **kwds ):
|
||||
def index( self, trans, **kwargs ):
|
||||
"""
|
||||
GET /api/visualizations:
|
||||
"""
|
||||
pass
|
||||
#TODO: search for vizsesses that apply to an object (sending model class and id? - how to do this?)
|
||||
rval = []
|
||||
try:
|
||||
if not trans.user:
|
||||
raise ItemAccessibilityException( 'You must be logged in to access visualizations' )
|
||||
user = trans.user
|
||||
|
||||
#TODO: search for: title, made by user, creation time range, type (vis name), dbkey, etc.
|
||||
#TODO: limit, offset, order_by
|
||||
#TODO: deleted
|
||||
|
||||
# this is the default search - user's vis, vis shared with user, published vis
|
||||
visualizations = self.get_visualizations_by_user( trans, user )
|
||||
visualizations += self.get_visualizations_shared_with_user( trans, user )
|
||||
visualizations += self.get_published_visualizations( trans, exclude_user=user )
|
||||
#TODO: the admin case - everything
|
||||
|
||||
for visualization in visualizations:
|
||||
item = self.get_visualization_summary_dict( visualization )
|
||||
item = trans.security.encode_dict_ids( item )
|
||||
item[ 'url' ] = web.url_for( 'visualization', id=item[ 'id' ] )
|
||||
rval.append( item )
|
||||
|
||||
except ItemAccessibilityException, exception:
|
||||
trans.response.status = 403
|
||||
rval = { 'error': str( exception ) }
|
||||
if trans.debug:
|
||||
log.exception( str( exception ) )
|
||||
|
||||
except Exception, exception:
|
||||
trans.response.status = 500
|
||||
rval = { 'error': str( exception ) }
|
||||
log.exception( 'visualizations index failed: %s' %( str( exception ) ) )
|
||||
|
||||
return rval
|
||||
|
||||
@web.json
|
||||
def show( self, trans, id, **kwd ):
|
||||
def show( self, trans, id, **kwargs ):
|
||||
"""
|
||||
GET /api/visualizations/{viz_id}
|
||||
"""
|
||||
pass
|
||||
#TODO: revisions should be a contents/nested controller like viz/xxx/r/xxx)?
|
||||
# the important thing is the config
|
||||
rval = {}
|
||||
#TODO:?? /api/visualizations/registry -> json of registry.listings?
|
||||
try:
|
||||
visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
|
||||
dictionary = trans.security.encode_dict_ids( self.get_visualization_dict( visualization ) )
|
||||
dictionary[ 'url' ] = url_for( controller='visualization',
|
||||
action="display_by_username_and_slug", username=visualization.user.username, slug=visualization.slug )
|
||||
dictionary[ 'annotation' ] = self.get_item_annotation_str( trans.sa_session, trans.user, visualization )
|
||||
|
||||
# need to encode ids in revisions as well
|
||||
encoded_revisions = []
|
||||
for revision in dictionary[ 'revisions' ]:
|
||||
#NOTE: does not encode ids inside the configs
|
||||
encoded_revisions.append( trans.security.encode_id( revision ) )
|
||||
dictionary[ 'revisions' ] = encoded_revisions
|
||||
dictionary[ 'latest_revision' ] = trans.security.encode_dict_ids( dictionary[ 'latest_revision' ] )
|
||||
|
||||
rval = dictionary
|
||||
|
||||
except ( ItemAccessibilityException, ItemDeletionException ), exception:
|
||||
trans.response.status = 403
|
||||
rval = { 'error': str( exception ) }
|
||||
if trans.debug:
|
||||
log.exception( 'visualization show forbidden (%s): %s' %( id, str( exception ) ) )
|
||||
|
||||
except Exception, exception:
|
||||
trans.response.status = 500
|
||||
rval = { 'error': str( exception ) }
|
||||
log.exception( 'visualization show failed (%s): %s' %( id, str( exception ) ) )
|
||||
|
||||
return rval
|
||||
|
||||
@web.expose_api
|
||||
def create( self, trans, payload, **kwd ):
|
||||
def create( self, trans, payload, **kwargs ):
|
||||
"""
|
||||
POST /api/visualizations
|
||||
creates a new visualization using the given payload
|
||||
|
||||
POST /api/visualizations?import_id={encoded_visualization_id}
|
||||
imports a copy of an existing visualization into the user's workspace
|
||||
"""
|
||||
pass
|
||||
rval = None
|
||||
try:
|
||||
if 'import_id' in payload:
|
||||
import_id = payload( 'import_id' )
|
||||
visualization = self.import_visualization( trans, import_id, user=trans.user )
|
||||
|
||||
else:
|
||||
payload = self._validate_and_parse_payload( payload )
|
||||
payload[ 'save' ] = True
|
||||
# create needs defaults like wizard needs food - generate defaults - this will err if given a weird key?
|
||||
visualization = self.create_visualization( trans, **payload )
|
||||
|
||||
rval = { 'id' : trans.security.encode_id( visualization.id ) }
|
||||
|
||||
#TODO: exception boilerplate
|
||||
except ( ItemAccessibilityException, ItemDeletionException ), exception:
|
||||
trans.response.status = 403
|
||||
rval = { 'error': str( exception ) }
|
||||
if trans.debug:
|
||||
log.exception( str( exception ) )
|
||||
|
||||
except ( ValueError, AttributeError ), exception:
|
||||
trans.response.status = 400
|
||||
rval = { 'error': str( exception ) }
|
||||
if trans.debug:
|
||||
log.exception( str( exception ) )
|
||||
|
||||
except Exception, exception:
|
||||
trans.response.status = 500
|
||||
rval = { 'error': str( exception ) }
|
||||
log.exception( 'creation of visualization failed: %s' %( str( exception ) ) )
|
||||
|
||||
return rval
|
||||
|
||||
@web.expose_api
|
||||
def update( self, trans, id, payload, **kwargs ):
|
||||
"""
|
||||
PUT /api/visualizations/{encoded_visualization_id}
|
||||
"""
|
||||
rval = None
|
||||
try:
|
||||
payload = self._validate_and_parse_payload( payload )
|
||||
|
||||
# there's a differentiation here between updating the visualiztion and creating a new revision
|
||||
# that needs to be handled clearly here
|
||||
# or alternately, using a different controller like PUT /api/visualizations/{id}/r/{id}
|
||||
|
||||
#TODO: consider allowing direct alteration of revisions title (without a new revision)
|
||||
# only create a new revsion on a different config
|
||||
|
||||
# only update owned visualizations
|
||||
visualization = self.get_visualization( trans, id, check_ownership=True )
|
||||
title = payload.get( 'title', visualization.latest_revision.title )
|
||||
dbkey = payload.get( 'dbkey', visualization.latest_revision.dbkey )
|
||||
config = payload.get( 'config', visualization.latest_revision.config )
|
||||
|
||||
latest_config = visualization.latest_revision.config
|
||||
if( ( title != visualization.latest_revision.title )
|
||||
or ( dbkey != visualization.latest_revision.dbkey )
|
||||
or ( util.json.to_json_string( config ) != util.json.to_json_string( latest_config ) ) ):
|
||||
revision = self.add_visualization_revision( trans, visualization, config, title, dbkey )
|
||||
#TODO: need to somehow get what changed here?
|
||||
rval = { 'id' : revision.id }
|
||||
|
||||
except ( ItemAccessibilityException, ItemDeletionException ), exception:
|
||||
trans.response.status = 403
|
||||
rval = { 'error': str( exception ) }
|
||||
if trans.debug:
|
||||
log.exception( str( exception ) )
|
||||
|
||||
except ( ValueError, AttributeError ), exception:
|
||||
trans.response.status = 400
|
||||
rval = { 'error': str( exception ) }
|
||||
if trans.debug:
|
||||
log.exception( str( exception ) )
|
||||
|
||||
except Exception, exception:
|
||||
trans.response.status = 500
|
||||
rval = { 'error': str( exception ) }
|
||||
log.exception( 'update of visualization (%s) failed: %s' %( id, str( exception ) ) )
|
||||
|
||||
return rval
|
||||
|
||||
def _validate_and_parse_payload( self, payload ):
|
||||
"""
|
||||
Validate and parse incomming data payload for a visualization.
|
||||
"""
|
||||
# This layer handles (most of the stricter idiot proofing):
|
||||
# - unknown/unallowed keys
|
||||
# - changing data keys from api key to attribute name
|
||||
# - protection against bad data form/type
|
||||
# - protection against malicious data content
|
||||
# all other conversions and processing (such as permissions, etc.) should happen down the line
|
||||
|
||||
# keys listed here don't error when attempting to set, but fail silently
|
||||
# this allows PUT'ing an entire model back to the server without attribute errors on uneditable attrs
|
||||
valid_but_uneditable_keys = (
|
||||
'id', 'model_class'
|
||||
#TODO: fill out when we create get_api_value, get_dict, whatevs
|
||||
)
|
||||
#TODO: deleted
|
||||
#TODO: importable
|
||||
|
||||
validated_payload = {}
|
||||
for key, val in payload.items():
|
||||
if key == 'config':
|
||||
if not isinstance( val, dict ):
|
||||
raise ValueError( '%s must be a dictionary (JSON): %s' %( key, str( type( val ) ) ) )
|
||||
|
||||
elif key == 'annotation':
|
||||
if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
|
||||
raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
|
||||
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
|
||||
|
||||
# these are keys that actually only be *updated* at the revision level and not here
|
||||
# (they are still valid for create, tho)
|
||||
elif key == 'title':
|
||||
if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
|
||||
raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
|
||||
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
|
||||
elif key == 'slug':
|
||||
if not isinstance( val, str ):
|
||||
raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) )
|
||||
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
|
||||
elif key == 'type':
|
||||
if not isinstance( val, str ):
|
||||
raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) )
|
||||
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
|
||||
#TODO: validate types in VALID_TYPES/registry names at the mixin/model level?
|
||||
elif key == 'dbkey':
|
||||
if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
|
||||
raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
|
||||
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
|
||||
|
||||
elif key not in valid_but_uneditable_keys:
|
||||
raise AttributeError( 'unknown key: %s' %( str( key ) ) )
|
||||
|
||||
validated_payload[ key ] = val
|
||||
return validated_payload
|
||||
|
||||
#@web.expose_api
|
||||
#def delete( self, trans, id, **kwd ):
|
||||
# """
|
||||
# DELETE /api/visualizations/{encoded_history_id}
|
||||
# Deletes a visualization from the database
|
||||
# """
|
||||
# pass
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/* Utility to load a specific page and output html, page text, or a screenshot
|
||||
* Optionally wait for some time, text, or dom selector
|
||||
*/
|
||||
try {
|
||||
//...if there's a better way - please let me know, universe
|
||||
var scriptDir = require( 'system' ).args[3]
|
||||
// remove the script filename
|
||||
.replace( /[\w|\.|\-|_]*$/, '' )
|
||||
// if given rel. path, prepend the curr dir
|
||||
.replace( /^(?!\/)/, './' ),
|
||||
spaceghost = require( scriptDir + 'spaceghost' ).create({
|
||||
// script options here (can be overridden by CLI)
|
||||
//verbose: true,
|
||||
//logLevel: debug,
|
||||
scriptDir: scriptDir
|
||||
});
|
||||
|
||||
} catch( error ){
|
||||
console.debug( error );
|
||||
phantom.exit( 1 );
|
||||
}
|
||||
spaceghost.start();
|
||||
|
||||
|
||||
// =================================================================== SET UP
|
||||
var utils = require( 'utils' );
|
||||
|
||||
var email = spaceghost.user.getRandomEmail(),
|
||||
password = '123456';
|
||||
if( spaceghost.fixtureData.testUser ){
|
||||
email = spaceghost.fixtureData.testUser.email;
|
||||
password = spaceghost.fixtureData.testUser.password;
|
||||
}
|
||||
spaceghost.user.loginOrRegisterUser( email, password );
|
||||
|
||||
function hasKeys( object, keysArray ){
|
||||
if( !utils.isObject( object ) ){ return false; }
|
||||
for( var i=0; i<keysArray.length; i += 1 ){
|
||||
if( !object.hasOwnProperty( keysArray[i] ) ){
|
||||
spaceghost.debug( 'object missing key: ' + keysArray[i] );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function countKeys( object ){
|
||||
if( !utils.isObject( object ) ){ return 0; }
|
||||
var count = 0;
|
||||
for( var key in object ){
|
||||
if( object.hasOwnProperty( key ) ){ count += 1; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function compareObjs( obj, compare_to, exclusionList ){
|
||||
exclusionList = exclusionList || [];
|
||||
for( var key in compare_to ){
|
||||
if( compare_to.hasOwnProperty( key ) && exclusionList.indexOf( key ) === -1 ){
|
||||
if( !obj.hasOwnProperty( key ) ){
|
||||
spaceghost.debug( 'obj missing key: ' + key );
|
||||
return false;
|
||||
}
|
||||
if( obj[ key ] !== compare_to[ key ] ){
|
||||
spaceghost.debug( 'obj value not equal. obj: ' + obj[ key ] + ', v.: ' + compare_to[ key ] );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function assertRaises( fn, expectedCode, errMsgShouldContain, assertionMsg, debug ){
|
||||
//TODO: errMsgShouldContain optional, any old error
|
||||
assertionMsg = assertionMsg || 'create returned error';
|
||||
debug = debug || false;
|
||||
var errorReturned = {};
|
||||
try {
|
||||
fn.call( spaceghost );
|
||||
} catch( error ){
|
||||
errorReturned = error;
|
||||
}
|
||||
if( errorReturned ){
|
||||
if( debug ){ spaceghost.debug( 'error:\n' + spaceghost.jsonStr( errorReturned ) ); }
|
||||
var errorMsg = errorReturned.message || '';
|
||||
if( errorMsg ){
|
||||
try {
|
||||
errorMsg = JSON.parse( errorReturned.message ).error;
|
||||
} catch( parseErr ){
|
||||
spaceghost.warn( 'error parsing error.message' );
|
||||
}
|
||||
}
|
||||
var properCode = errorReturned.status === expectedCode,
|
||||
properMsg = errorMsg.indexOf( errMsgShouldContain ) !== -1;
|
||||
spaceghost.test.assert( properCode && properMsg,
|
||||
assertionMsg + ': (' + errorReturned.status + ') "' + errorMsg + '"' );
|
||||
if( !( properCode && properMsg ) ){
|
||||
throw errorReturned;
|
||||
}
|
||||
} else {
|
||||
spaceghost.test.fail( assertionMsg + ': (no error)' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =================================================================== TESTS
|
||||
spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
|
||||
var ALWAYS_CREATE = true,
|
||||
indexKeys = [
|
||||
'id', 'title', 'type', 'dbkey', 'url'
|
||||
],
|
||||
showKeys = indexKeys.concat([
|
||||
'user_id', 'model_class', 'revisions', 'latest_revision', 'annotation'
|
||||
]),
|
||||
revisionKeys = [
|
||||
'id', 'title', 'visualization_id', 'dbkey', 'model_class', 'config'
|
||||
];
|
||||
|
||||
// ------------------------------------------------------------------------------------------- set up
|
||||
var visualizationIndex = this.api.visualizations.index();
|
||||
if( ALWAYS_CREATE || !visualizationIndex.length ){
|
||||
// post a visualization
|
||||
this.info( 'creating new visualization for tests' );
|
||||
var testVisualization = this.api.visualizations.create({
|
||||
title : 'Test Visualization',
|
||||
// needs to be unique
|
||||
slug : 'test-visualization-' + Date.now(),
|
||||
type : 'test',
|
||||
dbkey : 'hg17',
|
||||
annotation : 'this is a test of the emergency visualization system',
|
||||
config : {
|
||||
x : 10,
|
||||
y : 12
|
||||
}
|
||||
});
|
||||
this.debug( this.jsonStr( testVisualization ) );
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------- INDEX
|
||||
this.test.comment( 'index should get a list of visualizations' );
|
||||
visualizationIndex = this.api.visualizations.index();
|
||||
this.debug( this.jsonStr( visualizationIndex ) );
|
||||
this.test.assert( utils.isArray( visualizationIndex ),
|
||||
"index returned an array: length " + visualizationIndex.length );
|
||||
this.test.assert( visualizationIndex.length >= 1, 'Has at least one visualization' );
|
||||
|
||||
var firstVisualization = visualizationIndex[0];
|
||||
this.test.assert( hasKeys( firstVisualization, indexKeys ), 'Has the proper keys' );
|
||||
this.test.assert( this.api.isEncodedId( firstVisualization.id ), 'Id appears well-formed' );
|
||||
|
||||
//TODO: index searching
|
||||
//TODO: anon user
|
||||
//TODO: admin user
|
||||
|
||||
// ------------------------------------------------------------------------------------------- SHOW
|
||||
this.test.comment( 'show should get a visualization details object' );
|
||||
var visualizationShow = this.api.visualizations.show( firstVisualization.id );
|
||||
this.debug( this.jsonStr( visualizationShow ) );
|
||||
this.test.assert( hasKeys( visualizationShow, showKeys ), 'Has the proper keys' );
|
||||
this.test.assert( visualizationShow.model_class === 'Visualization',
|
||||
'Has the proper model_class: ' + visualizationShow.model_class );
|
||||
|
||||
this.test.comment( 'a visualization details object should contain an array of revision ids' );
|
||||
var revisions = visualizationShow.revisions;
|
||||
this.test.assert( utils.isArray( revisions ), 'revisions is an array' );
|
||||
this.test.assert( revisions.length >= 1, 'revisions has at least one entry' );
|
||||
var areIds = true;
|
||||
revisions.forEach( function( revision ){
|
||||
if( !spaceghost.api.isEncodedId( revision ) ){ areIds = false; }
|
||||
});
|
||||
this.test.assert( areIds, 'all revisions are ids' );
|
||||
|
||||
this.test.comment( 'a visualization details object should contain a subobject of the latest revision' );
|
||||
var latestRevision = visualizationShow.latest_revision;
|
||||
this.test.assert( utils.isObject( latestRevision ), 'latestRevision is an object' );
|
||||
this.test.assert( hasKeys( latestRevision, revisionKeys ), 'latestRevision has the proper keys' );
|
||||
this.test.assert( latestRevision.model_class === 'VisualizationRevision',
|
||||
'Has the proper model_class: ' + latestRevision.model_class );
|
||||
this.test.assert( latestRevision.visualization_id === visualizationShow.id,
|
||||
'revision visualization_id matches containing visualization id: ' + latestRevision.visualization_id );
|
||||
this.test.assert( visualizationShow.revisions.indexOf( latestRevision.id ) !== -1,
|
||||
'found latest_revision id in revisions' );
|
||||
|
||||
this.test.comment( 'a visualization revision should contain a subobject for the config' );
|
||||
var config = latestRevision.config;
|
||||
this.test.assert( utils.isObject( config ), 'config is an object:\n' + this.jsonStr( config ) );
|
||||
|
||||
//TODO: url in visualizationIndex == show url
|
||||
//TODO: non existing id throws error
|
||||
//TODO: anon user
|
||||
//TODO: user1 has no permissions to show user2
|
||||
|
||||
// ------------------------------------------------------------------------------------------- CREATE
|
||||
this.test.comment( 'Calling create should create a new visualization and allow setting the name' );
|
||||
var visualizationData = {
|
||||
title : 'Created Visualization',
|
||||
// needs to be unique
|
||||
slug : 'created-visualization-' + Date.now(),
|
||||
type : 'test',
|
||||
dbkey : 'hg17',
|
||||
annotation : 'invisible visualization',
|
||||
config : {
|
||||
x : 10,
|
||||
y : 12
|
||||
}
|
||||
};
|
||||
var created = this.api.visualizations.create( visualizationData );
|
||||
this.debug( 'returned from create:\n' + this.jsonStr( created ) );
|
||||
this.test.assert( this.api.isEncodedId( created.id ), "create returned an id: " + created.id );
|
||||
|
||||
// check v. show
|
||||
visualizationShow = this.api.visualizations.show( created.id );
|
||||
this.debug( 'visualizationShow:\n' + this.jsonStr( visualizationShow ) );
|
||||
// config is re-located into a revision and won't be there
|
||||
this.test.assert( compareObjs( visualizationShow, visualizationData, [ 'config' ] ),
|
||||
"show results seem to match create data" );
|
||||
|
||||
// the following errors are produced within base.controller.UsesVisualizationsMixin._create_visualization
|
||||
this.test.comment( 'Calling create with a non-unique slug will cause an API error' );
|
||||
assertRaises( function(){
|
||||
created = this.api.visualizations.create( visualizationData );
|
||||
}, 400, 'visualization identifier must be unique' );
|
||||
|
||||
this.test.comment( 'Calling create with no title will cause an API error' );
|
||||
visualizationData.title = '';
|
||||
assertRaises( function(){
|
||||
created = this.api.visualizations.create( visualizationData );
|
||||
}, 400, 'visualization name is required' );
|
||||
visualizationData.title = 'Created Visualization';
|
||||
|
||||
this.test.comment( 'Calling create with improper characters in the slug will cause an API error' );
|
||||
var oldSlug = visualizationData.slug;
|
||||
visualizationData.slug = '123_()';
|
||||
assertRaises( function(){
|
||||
created = this.api.visualizations.create( visualizationData );
|
||||
}, 400, "visualization identifier must consist of only lowercase letters, numbers, and the '-' character" );
|
||||
visualizationData.slug = oldSlug;
|
||||
|
||||
this.test.comment( 'Calling create with an unrecognized key will cause an API error' );
|
||||
visualizationData.bler = 'blah';
|
||||
assertRaises( function(){
|
||||
created = this.api.visualizations.create( visualizationData );
|
||||
}, 400, 'unknown key: bler' );
|
||||
delete visualizationData.bler;
|
||||
|
||||
this.test.comment( 'Calling create with an unparsable JSON config will cause an API error' );
|
||||
visualizationData.config = '3 = nime';
|
||||
assertRaises( function(){
|
||||
created = this.api.visualizations.create( visualizationData );
|
||||
}, 400, 'config must be a dictionary (JSON)' );
|
||||
|
||||
// ------------------------------------------------------------------------------------------ UPDATE
|
||||
// ........................................................................................... idiot proofing
|
||||
this.test.comment( 'updating using a nonsense key should fail with an error' );
|
||||
assertRaises( function(){
|
||||
returned = this.api.visualizations.update( created.id, { bler : 'blah' });
|
||||
}, 400, 'unknown key: bler' );
|
||||
|
||||
this.test.comment( 'updating by attempting to change type should cause an error' );
|
||||
assertRaises( function(){
|
||||
returned = this.api.visualizations.update( created.id, { title : 30 });
|
||||
}, 400, 'title must be a string or unicode' );
|
||||
//TODO: the other types...
|
||||
|
||||
// ........................................................................................... title
|
||||
//this.test.comment( 'update should create a new visualization revision' );
|
||||
//
|
||||
//this.test.comment( 'updating with a new title should NOT change the visualization title...' );
|
||||
//latestRevision = this.api.visualizations.show( created.id ).latest_revision;
|
||||
//returned = this.api.visualizations.update( created.id, {
|
||||
// title : 'New title'
|
||||
//});
|
||||
//visualizationShow = this.api.visualizations.show( created.id );
|
||||
//this.debug( this.jsonStr( visualizationShow ) );
|
||||
//this.test.assert( visualizationShow.title === visualizationData.title,
|
||||
// "Title does not set via update: " + visualizationShow.title );
|
||||
|
||||
});
|
||||
|
||||
// ===================================================================
|
||||
spaceghost.run( function(){
|
||||
});
|
||||
@@ -24,6 +24,7 @@ var API = function API( spaceghost, apikey ){
|
||||
this.tools = new ToolsAPI( this );
|
||||
this.workflows = new WorkflowsAPI( this );
|
||||
this.users = new UsersAPI( this );
|
||||
this.visualizations = new VisualizationsAPI( this );
|
||||
};
|
||||
exports.API = API;
|
||||
|
||||
@@ -73,6 +74,7 @@ API.prototype._ajax = function _ajax( url, options ){
|
||||
if( resp.status !== 200 ){
|
||||
// grrr... this doesn't lose the \n\r\t
|
||||
//throw new APIError( resp.responseText.replace( /[\s\n\r\t]+/gm, ' ' ).replace( /"/, '' ) );
|
||||
this.spaceghost.debug( 'api error response status code: ' + resp.status );
|
||||
throw new APIError( resp.responseText, resp.status );
|
||||
}
|
||||
return JSON.parse( resp.responseText );
|
||||
@@ -490,3 +492,57 @@ UsersAPI.prototype.update = function create( id, payload ){
|
||||
};
|
||||
|
||||
|
||||
// =================================================================== HISTORIES
|
||||
var VisualizationsAPI = function VisualizationsAPI( api ){
|
||||
this.api = api;
|
||||
};
|
||||
VisualizationsAPI.prototype.toString = function toString(){
|
||||
return this.api + '.VisualizationsAPI';
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
VisualizationsAPI.prototype.urlTpls = {
|
||||
index : 'api/visualizations',
|
||||
show : 'api/visualizations/%s',
|
||||
create : 'api/visualizations',
|
||||
//delete_ : 'api/visualizations/%s',
|
||||
//undelete: 'api/visualizations/deleted/%s/undelete',
|
||||
update : 'api/visualizations/%s'
|
||||
};
|
||||
|
||||
VisualizationsAPI.prototype.index = function index(){
|
||||
this.api.spaceghost.info( 'visualizations.index' );
|
||||
|
||||
return this.api._ajax( this.urlTpls.index );
|
||||
};
|
||||
|
||||
VisualizationsAPI.prototype.show = function show( id ){
|
||||
this.api.spaceghost.info( 'visualizations.show' );
|
||||
|
||||
return this.api._ajax( utils.format( this.urlTpls.show, this.api.ensureId( id ) ) );
|
||||
};
|
||||
|
||||
VisualizationsAPI.prototype.create = function create( payload ){
|
||||
this.api.spaceghost.info( 'visualizations.create: ' + this.api.spaceghost.jsonStr( payload ) );
|
||||
|
||||
// py.payload <-> ajax.data
|
||||
payload = this.api.ensureObject( payload );
|
||||
return this.api._ajax( utils.format( this.urlTpls.create ), {
|
||||
type : 'POST',
|
||||
data : payload
|
||||
});
|
||||
};
|
||||
|
||||
VisualizationsAPI.prototype.update = function create( id, payload ){
|
||||
this.api.spaceghost.info( 'visualizations.update: ' + id + ',' + this.api.spaceghost.jsonStr( payload ) );
|
||||
|
||||
// py.payload <-> ajax.data
|
||||
id = this.api.ensureId( id );
|
||||
payload = this.api.ensureObject( payload );
|
||||
url = utils.format( this.urlTpls.update, id );
|
||||
|
||||
return this.api._ajax( url, {
|
||||
type : 'PUT',
|
||||
data : payload
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user