1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/0698fc666bd0/ changeset: 0698fc666bd0 user: greg date: 2012-10-18 21:47:06 summary: Add the ability to review a defined (but flexible) set of repository components for repositories in the tool shed. Reviews are performed on specific revisions of the repository that are installable at the time of review. Each repository component can be reviewed and approved (or not) as well as rated. The repository revision is automatically rated as the average of all component ratings. The repository revision can be approved (or not) in addition to each component. Component reviews can be marked "private", in which case they are only accessible by the owner and the group of reviewers. The ability to review repositories is restricted by the tool shed's role-base access security components. Initially, all member of the Intergalactic Utilities Commission can review repositories. affected #: 24 files diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/controllers/admin.py --- a/lib/galaxy/webapps/community/controllers/admin.py +++ b/lib/galaxy/webapps/community/controllers/admin.py @@ -699,7 +699,7 @@ successful_count = 0 unsuccessful_count = 0 for repository_name_owner_str in repository_names_by_owner: - repository_name_owner_list = repository_name_owner_str.split( '__ESEP__' ) + repository_name_owner_list = repository_name_owner_str.split( STRSEP ) name = repository_name_owner_list[ 0 ] owner = repository_name_owner_list[ 1 ] repository = get_repository_by_name_and_owner( trans, name, owner ) @@ -737,7 +737,7 @@ trans.model.Repository.table.c.user_id ): owner = repository.user.username option_label = '%s (%s)' % ( repository.name, owner ) - option_value = '%s__ESEP__%s' % ( repository.name, owner ) + option_value = '%s%s%s' % ( repository.name, STRSEP, owner ) repositories_select_field.add_option( option_label, option_value ) return trans.fill_template( '/webapps/community/admin/reset_metadata_on_selected_repositories.mako', repositories_select_field=repositories_select_field, diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/controllers/common.py --- a/lib/galaxy/webapps/community/controllers/common.py +++ b/lib/galaxy/webapps/community/controllers/common.py @@ -79,6 +79,9 @@ '${host}' """ +# String separator +STRSEP = '__ESEP__' + # States for passing messages SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error" @@ -150,6 +153,15 @@ if repository_metadata: return repository_metadata.malicious return False +def changeset_revision_reviewed_by_user( trans, user, repository, changeset_revision ): + """Determine if the current changeset revision has been reviewed by the current user.""" + changeset_revision_reviewed_by_user = False + for reviewed_revision in repository.reviewed_revisions: + if reviewed_revision.changeset_revision == changeset_revision: + for review in repository.reviews: + if review.changeset_revision == changeset_revision and review.user == user: + return True + return False def check_file_contents( trans ): # See if any admin users have chosen to receive email alerts when a repository is updated. # If so, the file contents of the update must be checked for inappropriate content. @@ -386,7 +398,28 @@ """Get all categories from the database""" return trans.sa_session.query( trans.model.Category ) \ .filter( trans.model.Category.table.c.deleted==False ) \ - .order_by( trans.model.Category.table.c.name ).all() + .order_by( trans.model.Category.table.c.name ) \ + .all() +def get_component( trans, id ): + """Get a component from the database""" + return trans.sa_session.query( trans.model.Component ).get( trans.security.decode_id( id ) ) +def get_component_by_name( trans, name ): + return trans.sa_session.query( trans.app.model.Component ) \ + .filter( trans.app.model.Component.table.c.name==name ) \ + .first() +def get_component_review( trans, id ): + """Get a component_review from the database""" + return trans.sa_session.query( trans.model.ComponentReview ).get( trans.security.decode_id( id ) ) +def get_component_review_by_repository_review_id_component_id( trans, repository_review_id, component_id ): + """Get a component_review from the database via repository_review_id and component_id""" + return trans.sa_session.query( trans.model.ComponentReview ) \ + .filter( and_( trans.model.ComponentReview.table.c.repository_review_id == trans.security.decode_id( repository_review_id ), + trans.model.ComponentReview.table.c.component_id == trans.security.decode_id( component_id ) ) ) \ + .first() +def get_components( trans ): + return trans.sa_session.query( trans.app.model.Component ) \ + .order_by( trans.app.model.Component.name ) \ + .all() def get_latest_repository_metadata( trans, decoded_repository_id ): """Get last metadata defined for a specified repository from the database""" return trans.sa_session.query( trans.model.RepositoryMetadata ) \ @@ -469,6 +502,46 @@ return INITIAL_CHANGELOG_HASH else: previous_changeset_revision = current_changeset_revision +def get_previous_repository_reviews( trans, repository, changeset_revision ): + """Return an ordered dictionary of repository reviews up to and including the received changeset revision.""" + repo = hg.repository( get_configured_ui(), repository.repo_path ) + reviewed_revision_hashes = [ reviewed_revisions.changeset_revision for reviewed_revisions in repository.reviewed_revisions ] + previous_reviews_dict = odict() + for changeset in reversed_upper_bounded_changelog( repo, changeset_revision ): + previous_changeset_revision = str( repo.changectx( changeset ) ) + if previous_changeset_revision in reviewed_revision_hashes: + previous_rev, previous_changeset_revision_label = get_rev_label_from_changeset_revision( repo, previous_changeset_revision ) + revision_reviews = get_reviews_by_repository_id_changeset_revision( trans, trans.security.encode_id( repository.id ), previous_changeset_revision ) + previous_reviews_dict[ previous_changeset_revision ] = dict( changeset_revision_label=previous_changeset_revision_label, + reviews=revision_reviews ) + return previous_reviews_dict +def get_rev_label_changeset_revision_from_repository_metadata( repository_metadata, repository=None ): + if repository is None: + repository = repository_metadata.repository + repo = hg.repository( get_configured_ui(), repository.repo_path ) + changeset_revision = repository_metadata.changeset_revision + ctx = get_changectx_for_changeset( repo, changeset_revision ) + if ctx: + rev = '%04d' % ctx.rev() + label = "%s:%s" % ( str( ctx.rev() ), changeset_revision ) + else: + rev = '-1' + label = "-1:%s" % changeset_revision + return rev, label, changeset_revision +def get_rev_label_from_changeset_revision( repo, changeset_revision ): + ctx = get_changectx_for_changeset( repo, changeset_revision ) + if ctx: + rev = '%04d' % ctx.rev() + label = "%s:%s" % ( str( ctx.rev() ), changeset_revision ) + else: + rev = '-1' + label = "-1:%s" % changeset_revision + return rev, label +def get_reversed_changelog_changesets( repo ): + reversed_changelog = [] + for changeset in repo.changelog: + reversed_changelog.insert( 0, changeset ) + return reversed_changelog def get_repository( trans, id ): """Get a repository from the database via id""" return trans.sa_session.query( trans.model.Repository ).get( trans.security.decode_id( id ) ) @@ -489,6 +562,41 @@ """Get all metadata records for a specified repository.""" return trans.sa_session.query( trans.model.RepositoryMetadata ) \ .filter( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ) ) +def get_repository_metadata_revisions_for_review( repository, reviewed=True ): + repository_metadata_revisions = [] + metadata_changeset_revision_hashes = [] + if reviewed: + for metadata_revision in repository.metadata_revisions: + metadata_changeset_revision_hashes.append( metadata_revision.changeset_revision ) + for review in repository.reviews: + if review.changeset_revision in metadata_changeset_revision_hashes: + rmcr_hashes = [ rmr.changeset_revision for rmr in repository_metadata_revisions ] + if review.changeset_revision not in rmcr_hashes: + repository_metadata_revisions.append( review.repository_metadata ) + else: + for review in repository.reviews: + if review.changeset_revision not in metadata_changeset_revision_hashes: + metadata_changeset_revision_hashes.append( review.changeset_revision ) + for metadata_revision in repository.metadata_revisions: + if metadata_revision.changeset_revision not in metadata_changeset_revision_hashes: + repository_metadata_revisions.append( metadata_revision ) + return repository_metadata_revisions +def get_review( trans, id ): + """Get a repository_review from the database via id""" + return trans.sa_session.query( trans.model.RepositoryReview ).get( trans.security.decode_id( id ) ) +def get_review_by_repository_id_changeset_revision_user_id( trans, repository_id, changeset_revision, user_id ): + """Get a repository_review from the database via repository id, changeset_revision and user_id""" + return trans.sa_session.query( trans.model.RepositoryReview ) \ + .filter( and_( trans.model.RepositoryReview.repository_id == trans.security.decode_id( repository_id ), + trans.model.RepositoryReview.changeset_revision == changeset_revision, + trans.model.RepositoryReview.user_id == trans.security.decode_id( user_id ) ) ) \ + .first() +def get_reviews_by_repository_id_changeset_revision( trans, repository_id, changeset_revision ): + """Get all repository_reviews from the database via repository id and changeset_revision""" + return trans.sa_session.query( trans.model.RepositoryReview ) \ + .filter( and_( trans.model.RepositoryReview.repository_id == trans.security.decode_id( repository_id ), + trans.model.RepositoryReview.changeset_revision == changeset_revision ) ) \ + .all() def get_revision_label( trans, repository, changeset_revision ): """ Return a string consisting of the human read-able @@ -590,6 +698,15 @@ util.send_mail( frm, to, subject, body, trans.app.config ) except Exception, e: log.exception( "An error occurred sending a tool shed repository update alert by email." ) +def has_previous_repository_reviews( trans, repository, changeset_revision ): + """Determine if a repository has a changeset revision review prior to the received changeset revision.""" + repo = hg.repository( get_configured_ui(), repository.repo_path ) + reviewed_revision_hashes = [ reviewed_revisions.changeset_revision for reviewed_revisions in repository.reviewed_revisions ] + for changeset in reversed_upper_bounded_changelog( repo, changeset_revision ): + previous_changeset_revision = str( repo.changectx( changeset ) ) + if previous_changeset_revision in reviewed_revision_hashes: + return True + return False def is_downloadable( metadata_dict ): return 'datatypes' in metadata_dict or 'tools' in metadata_dict or 'workflows' in metadata_dict def load_tool_from_changeset_revision( trans, repository_id, changeset_revision, tool_config_filename ): diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/controllers/repository.py --- a/lib/galaxy/webapps/community/controllers/repository.py +++ b/lib/galaxy/webapps/community/controllers/repository.py @@ -110,14 +110,42 @@ def __init__( self, col_name ): grids.GridColumn.__init__( self, col_name ) def get_value( self, trans, grid, repository ): - """Display a SelectField whose options are the changeset_revision strings of all revisions of this repository.""" + """Display a SelectField whose options are the changeset_revision strings of all metadata revisions of this repository.""" # A repository's metadata revisions may not all be installable, as some may contain only invalid tools. - select_field = build_changeset_revision_select_field( trans, repository, downloadable_only=False ) + select_field = build_changeset_revision_select_field( trans, repository, downloadable=False ) if len( select_field.options ) > 1: return select_field.get_html() elif len( select_field.options ) == 1: return select_field.options[ 0 ][ 0 ] return '' + class WithReviewsRevisionColumn( grids.GridColumn ): + def __init__( self, col_name ): + grids.GridColumn.__init__( self, col_name ) + def get_value( self, trans, grid, repository ): + # Restrict to revisions that have been reviewed. + repository_metadata_revisions = get_repository_metadata_revisions_for_review( repository, reviewed=True ) + if repository_metadata_revisions: + rval = '' + for repository_metadata in repository_metadata_revisions: + rev, label, changeset_revision = get_rev_label_changeset_revision_from_repository_metadata( repository_metadata, repository=repository ) + rval += '<a href="manage_repository_reviews_of_revision' + rval += '?id=%s&changeset_revision=%s">%s</a><br/>' % ( trans.security.encode_id( repository.id ), changeset_revision, label ) + return rval + return '' + class WithoutReviewsRevisionColumn( grids.GridColumn ): + def __init__( self, col_name ): + grids.GridColumn.__init__( self, col_name ) + def get_value( self, trans, grid, repository ): + # Restrict the options to revisions that have not yet been reviewed. + repository_metadata_revisions = get_repository_metadata_revisions_for_review( repository, reviewed=False ) + if repository_metadata_revisions: + rval = '' + for repository_metadata in repository_metadata_revisions: + rev, label, changeset_revision = get_rev_label_changeset_revision_from_repository_metadata( repository_metadata, repository=repository ) + rval += '<a href="manage_repository_reviews_of_revision' + rval += '?id=%s&changeset_revision=%s">%s</a><br/>' % ( trans.security.encode_id( repository.id ), changeset_revision, label ) + return rval + return '' class TipRevisionColumn( grids.GridColumn ): def __init__( self, col_name ): grids.GridColumn.__init__( self, col_name ) @@ -224,8 +252,7 @@ columns = [ RepositoryListGrid.NameColumn( "Name", key="name", - link=( lambda item: dict( operation="view_or_manage_repository", - id=item.id ) ), + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), attach_popup=False ), RepositoryListGrid.DescriptionColumn( "Synopsis", key="description", @@ -295,7 +322,7 @@ grids.GridColumn.__init__( self, col_name ) def get_value( self, trans, grid, repository ): """Display a SelectField whose options are the changeset_revision strings of all download-able revisions of this repository.""" - select_field = build_changeset_revision_select_field( trans, repository, downloadable_only=True ) + select_field = build_changeset_revision_select_field( trans, repository, downloadable=True ) if len( select_field.options ) > 1: return select_field.get_html() elif len( select_field.options ) == 1: @@ -511,17 +538,9 @@ if 'operation' in kwd: operation = kwd['operation'].lower() if operation == "view_or_manage_repository": - repository_id = kwd[ 'id' ] - repository = get_repository( trans, repository_id ) - is_admin = trans.user_is_admin() - if is_admin or repository.user == trans.user: - return trans.response.send_redirect( web.url_for( controller='repository', - action='manage_repository', - **kwd ) ) - else: - return trans.response.send_redirect( web.url_for( controller='repository', - action='view_repository', - **kwd ) ) + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) elif operation == "edit_repository": return trans.response.send_redirect( web.url_for( controller='repository', action='edit_repository', @@ -546,6 +565,9 @@ if k.startswith( 'f-' ): del kwd[ k ] kwd[ 'f-email' ] = trans.user.email + elif operation == "reviewed_repositories_i_own": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='reviewed_repositories_i_own' ) ) elif operation == "writable_repositories": kwd[ 'username' ] = trans.user.username return self.writable_repository_list_grid( trans, **kwd ) @@ -1385,8 +1407,17 @@ status = params.get( 'status', 'done' ) # See if there are any RepositoryMetadata records since menu items require them. repository_metadata = trans.sa_session.query( model.RepositoryMetadata ).first() + # See if the current user owns any repositories that have been reviewed. + has_reviewed_repositories = False + current_user = trans.user + if current_user: + for repository in current_user.active_repositories: + if repository.reviewed_revisions: + has_reviewed_repositories = True + break return trans.fill_template( '/webapps/community/index.mako', repository_metadata=repository_metadata, + has_reviewed_repositories=has_reviewed_repositories, message=message, status=status ) @web.expose @@ -1508,6 +1539,7 @@ params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) + cntrller = params.get( 'cntrller', 'repository' ) repository = get_repository( trans, id ) repo_dir = repository.repo_path repo = hg.repository( get_configured_ui(), repo_dir ) @@ -1614,7 +1646,7 @@ repository, selected_value=changeset_revision, add_id_to_name=False, - downloadable_only=False ) + downloadable=False ) revision_label = get_revision_label( trans, repository, repository.tip ) repository_metadata_id = None metadata = None @@ -1645,7 +1677,10 @@ malicious_check_box = CheckboxField( 'malicious', checked=is_malicious ) categories = get_categories( trans ) selected_categories = [ rca.category_id for rca in repository.categories ] + # Determine if the current changeset revision has been reviewed by the current user. + reviewed_by_user = changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) return trans.fill_template( '/webapps/community/repository/manage_repository.mako', + cntrller=cntrller, repo_name=repo_name, description=description, long_description=long_description, @@ -1655,6 +1690,7 @@ repository=repository, repository_metadata_id=repository_metadata_id, changeset_revision=changeset_revision, + reviewed_by_user=reviewed_by_user, changeset_revision_select_field=changeset_revision_select_field, revision_label=revision_label, selected_categories=selected_categories, @@ -1669,6 +1705,12 @@ message=message, status=status ) @web.expose + @web.require_login( "review repository revision" ) + def manage_repository_reviews_of_revision( self, trans, mine=False, **kwd ): + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='manage_repository_reviews_of_revision', + **kwd ) ) + @web.expose @web.require_login( "multi select email alerts" ) def multi_select_email_alerts( self, trans, **kwd ): params = util.Params( kwd ) @@ -1726,7 +1768,7 @@ repository, selected_value=changeset_revision, add_id_to_name=False, - downloadable_only=False ) + downloadable=False ) return trans.fill_template( '/webapps/community/repository/preview_tools_in_changeset.mako', repository=repository, repository_metadata_id=repository_metadata_id, @@ -2170,6 +2212,17 @@ message=message, status=status ) @web.expose + def view_or_manage_repository( self, trans, **kwd ): + repository = get_repository( trans, kwd[ 'id' ] ) + if trans.user_is_admin() or repository.user == trans.user: + return trans.response.send_redirect( web.url_for( controller='repository', + action='manage_repository', + **kwd ) ) + else: + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_repository', + **kwd ) ) + @web.expose def view_readme( self, trans, id, changeset_revision, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) @@ -2225,6 +2278,7 @@ params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) + cntrller = params.get( 'cntrller', 'repository' ) repository = get_repository( trans, id ) repo = hg.repository( get_configured_ui(), repository.repo_path ) avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model ) @@ -2258,7 +2312,7 @@ repository, selected_value=changeset_revision, add_id_to_name=False, - downloadable_only=False ) + downloadable=False ) revision_label = get_revision_label( trans, repository, changeset_revision ) repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision ) if repository_metadata: @@ -2274,7 +2328,10 @@ else: message += malicious_error status = 'error' + # Determine if the current changeset revision has been reviewed by the current user. + reviewed_by_user = changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) return trans.fill_template( '/webapps/community/repository/view_repository.mako', + cntrller=cntrller, repo=repo, repository=repository, repository_metadata_id=repository_metadata_id, @@ -2284,6 +2341,7 @@ num_ratings=num_ratings, alerts_check_box=alerts_check_box, changeset_revision=changeset_revision, + reviewed_by_user=reviewed_by_user, changeset_revision_select_field=changeset_revision_select_field, revision_label=revision_label, is_malicious=is_malicious, @@ -2342,8 +2400,9 @@ repository, selected_value=changeset_revision, add_id_to_name=False, - downloadable_only=False ) + downloadable=False ) trans.app.config.tool_data_path = original_tool_data_path + reviewed_by_user = changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) return trans.fill_template( "/webapps/community/repository/view_tool_metadata.mako", repository=repository, metadata=metadata, @@ -2354,29 +2413,44 @@ revision_label=revision_label, changeset_revision_select_field=changeset_revision_select_field, is_malicious=is_malicious, + reviewed_by_user=reviewed_by_user, message=message, status=status ) # ----- Utility methods ----- -def build_changeset_revision_select_field( trans, repository, selected_value=None, add_id_to_name=True, downloadable_only=False ): - """Build a SelectField whose options are the changeset_rev strings of all downloadable revisions of the received repository.""" - repo = hg.repository( get_configured_ui(), repository.repo_path ) + +def build_changeset_revision_select_field( trans, repository, selected_value=None, add_id_to_name=True, + downloadable=False, reviewed=False, not_reviewed=False ): + """Build a SelectField whose options are the changeset_rev strings of certain revisions of the received repository.""" options = [] changeset_tups = [] refresh_on_change_values = [] - if downloadable_only: + if downloadable: + # Restrict the options to downloadable revisions. repository_metadata_revisions = repository.downloadable_revisions + elif reviewed: + # Restrict the options to revisions that have been reviewed. + repository_metadata_revisions = [] + metadata_changeset_revision_hashes = [] + for metadata_revision in repository.metadata_revisions: + metadata_changeset_revision_hashes.append( metadata_revision.changeset_revision ) + for review in repository.reviews: + if review.changeset_revision in metadata_changeset_revision_hashes: + repository_metadata_revisions.append( review.repository_metadata ) + elif not_reviewed: + # Restrict the options to revisions that have not yet been reviewed. + repository_metadata_revisions = [] + reviewed_metadata_changeset_revision_hashes = [] + for review in repository.reviews: + reviewed_metadata_changeset_revision_hashes.append( review.changeset_revision ) + for metadata_revision in repository.metadata_revisions: + if metadata_revision.changeset_revision not in reviewed_metadata_changeset_revision_hashes: + repository_metadata_revisions.append( metadata_revision ) else: + # Restrict the options to all revisions that have associated metadata. repository_metadata_revisions = repository.metadata_revisions for repository_metadata in repository_metadata_revisions: - changeset_revision = repository_metadata.changeset_revision - ctx = get_changectx_for_changeset( repo, changeset_revision ) - if ctx: - rev = '%04d' % ctx.rev() - label = "%s:%s" % ( str( ctx.rev() ), changeset_revision ) - else: - rev = '-1' - label = "-1:%s" % changeset_revision + rev, label, changeset_revision = get_rev_label_changeset_revision_from_repository_metadata( repository_metadata, repository=repository ) changeset_tups.append( ( rev, label, changeset_revision ) ) refresh_on_change_values.append( changeset_revision ) # Sort options by the revision label. Even though the downloadable_revisions query sorts by update_time, diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/controllers/repository_review.py --- /dev/null +++ b/lib/galaxy/webapps/community/controllers/repository_review.py @@ -0,0 +1,707 @@ +import os, logging +from galaxy import util +from galaxy.web.base.controller import * +from galaxy.web.form_builder import SelectField, CheckboxField +from galaxy.webapps.community import model +from galaxy.web.framework.helpers import time_ago, iff, grids +from galaxy.model.orm import * +from sqlalchemy.sql.expression import func +from common import * +from repository import RepositoryListGrid +from galaxy.util.shed_util import get_configured_ui +from galaxy.util.odict import odict + +from galaxy import eggs +eggs.require('mercurial') +from mercurial import hg, ui, patch, commands + +log = logging.getLogger( __name__ ) + +class ComponentGrid( grids.Grid ): + class NameColumn( grids.TextColumn ): + def get_value( self, trans, grid, component ): + return component.name + class DescriptionColumn( grids.TextColumn ): + def get_value( self, trans, grid, component ): + return component.description + title = "Repository review components" + model_class = model.Component + template='/webapps/community/repository_review/grid.mako' + default_sort_key = "name" + columns = [ + NameColumn( "Name", + key="Component.name", + link=( lambda item: dict( operation="edit", id=item.id ) ), + attach_popup=False ), + DescriptionColumn( "Description", + key="Component.description", + attach_popup=False ) + ] + default_filter = {} + global_actions = [ + grids.GridAction( "Add new component", + dict( controller='repository_review', action='manage_components', operation='create' ) ) + ] + operations = [] + standard_filters = [] + num_rows_per_page = 50 + preserve_state = False + use_paging = True + +class RepositoriesWithReviewsGrid( RepositoryListGrid ): + class ReviewersColumn( grids.TextColumn ): + def get_value( self, trans, grid, repository ): + if repository.reviewers: + rval = '' + for user in repository.reviewers: + rval += '%s<br/>' % user.username + return rval + return '' + title = "All reviewed Repositories" + model_class = model.Repository + template='/webapps/community/repository_review/grid.mako' + default_sort_key = "Repository.name" + columns = [ + RepositoryListGrid.NameColumn( "Repository name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=True ), + RepositoryListGrid.DescriptionColumn( "Synopsis", + key="description", + attach_popup=False ), + RepositoryListGrid.WithReviewsRevisionColumn( "Reviewed revisions" ), + RepositoryListGrid.WithoutReviewsRevisionColumn( "Revisions for review" ), + RepositoryListGrid.UserColumn( "Owner", + attach_popup=False ), + ReviewersColumn( "Reviewers", + attach_popup=False ) + ] + operations = [ + grids.GridOperation( "Inspect repository revisions", + allow_multiple=False, + condition=( lambda item: not item.deleted ), + async_compatible=False ) + ] + def build_initial_query( self, trans, **kwd ): + return trans.sa_session.query( model.Repository ) \ + .join( ( model.RepositoryReview.table, model.RepositoryReview.table.c.repository_id == model.Repository.table.c.id ) ) \ + .join( ( model.User.table, model.User.table.c.id == model.Repository.table.c.user_id ) ) \ + .outerjoin( ( model.ComponentReview.table, model.ComponentReview.table.c.repository_review_id == model.RepositoryReview.table.c.id ) ) \ + .outerjoin( ( model.Component.table, model.Component.table.c.id == model.ComponentReview.table.c.component_id ) ) + +class RepositoriesWithoutReviewsGrid( RepositoriesWithReviewsGrid ): + title = "Repositories with no reviews" + columns = [ + RepositoriesWithReviewsGrid.NameColumn( "Repository name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=True ), + RepositoriesWithReviewsGrid.DescriptionColumn( "Synopsis", + key="description", + attach_popup=False ), + RepositoriesWithReviewsGrid.WithoutReviewsRevisionColumn( "Revisions for review" ), + RepositoriesWithReviewsGrid.UserColumn( "Owner", + attach_popup=False ) + ] + operations = [ grids.GridOperation( "Inspect repository revisions", + allow_multiple=False, + condition=( lambda item: not item.deleted ), + async_compatible=False ) ] + def build_initial_query( self, trans, **kwd ): + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.reviews == None ) \ + .join( model.User.table ) + +class RepositoriesReviewedByMeGrid( RepositoriesWithReviewsGrid ): + def build_initial_query( self, trans, **kwd ): + return trans.sa_session.query( model.Repository ) \ + .join( ( model.RepositoryReview.table, model.RepositoryReview.table.c.repository_id == model.Repository.table.c.id ) ) \ + .filter( model.RepositoryReview.table.c.user_id == trans.user.id ) \ + .join( ( model.User.table, model.User.table.c.id == model.RepositoryReview.table.c.user_id ) ) \ + .outerjoin( ( model.ComponentReview.table, model.ComponentReview.table.c.repository_review_id == model.RepositoryReview.table.c.id ) ) \ + .outerjoin( ( model.Component.table, model.Component.table.c.id == model.ComponentReview.table.c.component_id ) ) + +class RepositoryReviewsByUserGrid( grids.Grid ): + class RepositoryNameColumn( grids.TextColumn ): + def get_value( self, trans, grid, review ): + return review.repository.name + class RepositoryDescriptionColumn( grids.TextColumn ): + def get_value( self, trans, grid, review ): + return review.repository.description + class RevisionColumn( grids.TextColumn ): + def get_value( self, trans, grid, review ): + encoded_review_id = trans.security.encode_id( review.id ) + rval = '<a class="action-button" href="' + if review.user == trans.user: + rval += 'edit_review' + else: + rval +='browse_review' + rval += '?id=%s">%s</a>' % ( encoded_review_id, get_revision_label( trans, review.repository, review.changeset_revision ) ) + return rval + class RatingColumn( grids.TextColumn ): + def get_value( self, trans, grid, review ): + if review.rating: + rval = '<input ' + rval += 'name="star1-%s" ' % trans.security.encode_id( review.id ) + rval += 'type="radio" ' + rval += 'class="community_rating_star star" ' + rval += 'disabled="disabled" ' + rval += 'value="%s"' % str( review.rating ) + rval += '/>' + return rval + return '' + title = "Reviews by user" + model_class = model.RepositoryReview + template='/webapps/community/repository_review/grid.mako' + default_sort_key = 'repository_id' + columns = [ + RepositoryNameColumn( "Repository Name", + model_class=model.Repository, + key="Repository.name", + attach_popup=False ), + RepositoryDescriptionColumn( "Description", + model_class=model.Repository, + key="Repository.description", + attach_popup=False ), + RevisionColumn( "Revision", + attach_popup=False ), + RatingColumn( "Rating", + attach_popup=False ) + ] + # Override these + default_filter = {} + global_actions = [] + operations = [] + standard_filters = [] + num_rows_per_page = 50 + preserve_state = False + use_paging = True + def build_initial_query( self, trans, **kwd ): + user_id = trans.security.decode_id( kwd[ 'id' ] ) + return trans.sa_session.query( self.model_class ) \ + .filter( and_( model.RepositoryReview.table.c.deleted == False, \ + model.RepositoryReview.table.c.user_id == user_id ) ) + +class ReviewedRepositoriesIOwnGrid( RepositoriesWithReviewsGrid ): + def build_initial_query( self, trans, **kwd ): + return trans.sa_session.query( model.Repository ) \ + .join( ( model.RepositoryReview.table, model.RepositoryReview.table.c.repository_id == model.Repository.table.c.id ) ) \ + .filter( model.Repository.table.c.user_id == trans.user.id ) \ + .join( ( model.User.table, model.User.table.c.id == model.RepositoryReview.table.c.user_id ) ) \ + .outerjoin( ( model.ComponentReview.table, model.ComponentReview.table.c.repository_review_id == model.RepositoryReview.table.c.id ) ) \ + .outerjoin( ( model.Component.table, model.Component.table.c.id == model.ComponentReview.table.c.component_id ) ) + +class RepositoryReviewController( BaseUIController, ItemRatings ): + + component_grid = ComponentGrid() + repositories_reviewed_by_me_grid = RepositoriesReviewedByMeGrid() + repositories_with_reviews_grid = RepositoriesWithReviewsGrid() + repositories_without_reviews_grid = RepositoriesWithoutReviewsGrid() + repository_reviews_by_user_grid = RepositoryReviewsByUserGrid() + reviewed_repositories_i_own_grid = ReviewedRepositoriesIOwnGrid() + + @web.expose + @web.require_login( "approve repository review" ) + def approve_repository_review( self, trans, **kwd ): + # The value of the received id is the encoded review id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + encoded_review_id = kwd[ 'id' ] + review = get_review( trans, encoded_review_id ) + if kwd.get( 'approve_repository_review_button', False ): + approved_select_field_name = '%s%sapproved' % ( encoded_review_id, STRSEP ) + approved_select_field_value = str( kwd[ approved_select_field_name ] ) + review.approved = approved_select_field_value + trans.sa_session.add( review ) + trans.sa_session.flush() + message = 'Approved value <b>%s</b> saved for this revision.' % approved_select_field_value + repository_id = trans.security.encode_id( review.repository_id ) + changeset_revision = review.changeset_revision + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='manage_repository_reviews_of_revision', + id=repository_id, + changeset_revision=changeset_revision, + message=message, + status=status ) ) + @web.expose + @web.require_login( "browse components" ) + def browse_components( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "create": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='create_component', + **kwd ) ) + return self.component_grid( trans, **kwd ) + @web.expose + @web.require_login( "browse review" ) + def browse_review( self, trans, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + review = get_review( trans, kwd[ 'id' ] ) + repository = review.repository + repo = hg.repository( get_configured_ui(), repository.repo_path ) + rev, changeset_revision_label = get_rev_label_from_changeset_revision( repo, review.changeset_revision ) + return trans.fill_template( '/webapps/community/repository_review/browse_review.mako', + repository=repository, + changeset_revision_label=changeset_revision_label, + review=review, + message=message, + status=status ) + def copy_review( self, trans, review_to_copy, review ): + for component_review in review_to_copy.component_reviews: + copied_component_review = trans.model.ComponentReview( repository_review_id=review.id, + component_id=component_review.component.id, + comment=component_review.comment, + private=component_review.private, + approved=component_review.approved, + rating=component_review.rating ) + trans.sa_session.add( copied_component_review ) + trans.sa_session.flush() + review.approved = review_to_copy.approved + review.rating = review_to_copy.rating + trans.sa_session.add( review ) + trans.sa_session.flush() + @web.expose + @web.require_login( "create component" ) + def create_component( self, trans, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + name = util.restore_text( params.get( 'name', '' ) ) + description = util.restore_text( params.get( 'description', '' ) ) + if params.get( 'create_component_button', False ): + if not name or not description: + message = 'Enter a valid name and a description' + status = 'error' + elif get_component_by_name( trans, name ): + message = 'A component with that name already exists' + status = 'error' + else: + component = trans.app.model.Component( name=name, description=description ) + trans.sa_session.add( component ) + trans.sa_session.flush() + message = "Component '%s' has been created" % component.name + status = 'done' + trans.response.send_redirect( web.url_for( controller='repository_review', + action='manage_components', + message=message, + status=status ) ) + return trans.fill_template( '/webapps/community/repository_review/create_component.mako', + name=name, + description=description, + message=message, + status=status ) + @web.expose + @web.require_login( "create review" ) + def create_review( self, trans, **kwd ): + # The value of the received id is the encoded repository id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + repository_id = kwd.get( 'id', None ) + changeset_revision = kwd.get( 'changeset_revision', None ) + previous_review_id = kwd.get( 'previous_review_id', None ) + create_without_copying = 'create_without_copying' in kwd + if repository_id: + if changeset_revision: + # Make sure there is not already a review of the revision by the user. + if get_review_by_repository_id_changeset_revision_user_id( trans, + repository_id, + changeset_revision, + trans.security.encode_id( trans.user.id ) ): + message = "You have already created a review for revision <b>%s</b> of repository <b>%s</b>." % ( changeset_revision, repository.name ) + status = "error" + else: + repository = get_repository( trans, repository_id ) + # See if there are any reviews for previous changeset revisions that the user can copy. + if not create_without_copying and not previous_review_id and has_previous_repository_reviews( trans, repository, changeset_revision ): + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='select_previous_review', + **kwd ) ) + # A review can be initially performed only on an installable revision of a repository, so make sure we have metadata associated + # with the received changeset_revision. + repository_metadata = get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision ) + if repository_metadata: + metadata = repository_metadata.metadata + if metadata: + review = trans.app.model.RepositoryReview( repository_id=repository_metadata.repository_id, + changeset_revision=changeset_revision, + user_id=trans.user.id, + rating=None, + deleted=False ) + trans.sa_session.add( review ) + trans.sa_session.flush() + if previous_review_id: + review_to_copy = get_review( trans, previous_review_id ) + self.copy_review( trans, review_to_copy, review ) + review_id = trans.security.encode_id( review.id ) + message = "Begin your review of revision <b>%s</b> of repository <b>%s</b>." \ + % ( changeset_revision, repository.name ) + status = 'done' + trans.response.send_redirect( web.url_for( controller='repository_review', + action='edit_review', + id=review_id, + message=message, + status=status ) ) + else: + message = "A new review cannot be created for revision <b>%s</b> of repository <b>%s</b>. Select a valid revision and try again." \ + % ( changeset_revision, repository.name ) + kwd[ 'message' ] = message + kwd[ 'status' ] = 'error' + else: + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='manage_repository_reviews', + **kwd ) ) + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='view_or_manage_repository', + **kwd ) ) + @web.expose + @web.require_login( "edit component" ) + def edit_component( self, trans, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + id = params.get( 'id', None ) + if not id: + message = "No component ids received for editing" + trans.response.send_redirect( web.url_for( controller='admin', + action='manage_categories', + message=message, + status='error' ) ) + component = get_component( trans, id ) + if params.get( 'edit_component_button', False ): + new_description = util.restore_text( params.get( 'description', '' ) ).strip() + if component.description != new_description: + component.description = new_description + trans.sa_session.add( component ) + trans.sa_session.flush() + message = "The information has been saved for the component named <b>%s</b>" % ( component.name ) + status = 'done' + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='manage_components', + message=message, + status=status ) ) + return trans.fill_template( '/webapps/community/repository_review/edit_component.mako', + component=component, + message=message, + status=status ) + @web.expose + @web.require_login( "edit review" ) + def edit_review( self, trans, **kwd ): + # The value of the received id is the encoded review id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + review_id = kwd.get( 'id', None ) + review = get_review( trans, review_id ) + components_dict = odict() + for component in get_components( trans ): + components_dict[ component.name ] = dict( component=component, component_review=None ) + repository = review.repository + repo = hg.repository( get_configured_ui(), repository.repo_path ) + for component_review in review.component_reviews: + if component_review and component_review.component: + component_name = component_review.component.name + if component_name in components_dict: + component_review_dict = components_dict[ component_name ] + component_review_dict[ 'component_review' ] = component_review + components_dict[ component_name ] = component_review_dict + # Handle a Save button click. + save_button_clicked = False + save_buttons = [ '%s%sreview_button' % ( component_name, STRSEP ) for component_name in components_dict.keys() ] + save_buttons.append( 'revision_approved_button' ) + for save_button in save_buttons: + if save_button in kwd: + save_button_clicked = True + break + if save_button_clicked: + # Handle the revision_approved_select_field value. + revision_approved = kwd.get( 'revision_approved', None ) + revision_approved_setting_changed = False + if revision_approved: + revision_approved = str( revision_approved ) + if review.approved != revision_approved: + revision_approved_setting_changed = True + review.approved = revision_approved + trans.sa_session.add( review ) + trans.sa_session.flush() + saved_component_names = [] + for component_name in components_dict.keys(): + flushed = False + # Retrieve the review information from the form. + # The star rating form field is a radio button list, so it will not be received if it was not clicked in the form. + # Due to this behavior, default the value to 0. + rating = 0 + for k, v in kwd.items(): + if k.startswith( '%s%s' % ( component_name, STRSEP ) ): + component_review_attr = k.replace( '%s%s' % ( component_name, STRSEP ), '' ) + if component_review_attr == 'component_id': + component_id = str( v ) + elif component_review_attr == 'comment': + comment = str( v ) + elif component_review_attr == 'private': + private = CheckboxField.is_checked( str( v ) ) + elif component_review_attr == 'approved': + approved = str( v ) + elif component_review_attr == 'rating': + rating = int( str( v ) ) + component = get_component( trans, component_id ) + component_review = get_component_review_by_repository_review_id_component_id( trans, review_id, component_id ) + if component_review: + # See if the existing component review should be updated. + if component_review.comment != comment or \ + component_review.private != private or \ + component_review.approved != approved or \ + component_review.rating != rating: + component_review.comment = comment + component_review.private = private + component_review.approved = approved + component_review.rating = rating + trans.sa_session.add( component_review ) + trans.sa_session.flush() + flushed = True + saved_component_names.append( component_name ) + else: + # See if a new component_review should be created. + if comment or private or approved != trans.model.ComponentReview.approved_states.NO or rating: + component_review = trans.model.ComponentReview( repository_review_id=review.id, + component_id=component.id, + comment=comment, + approved=approved, + rating=rating ) + trans.sa_session.add( component_review ) + trans.sa_session.flush() + flushed = True + saved_component_names.append( component_name ) + if flushed: + # Update the repository rating value to be the average of all component review ratings. + average_rating = trans.sa_session.query( func.avg( trans.model.ComponentReview.table.c.rating ) ) \ + .filter( trans.model.ComponentReview.table.c.repository_review_id == review.id ) \ + .scalar() + review.rating = int( average_rating ) + trans.sa_session.add( review ) + trans.sa_session.flush() + # Update the information in components_dict. + if component_name in components_dict: + component_review_dict = components_dict[ component_name ] + component_review_dict[ 'component_review' ] = component_review + components_dict[ component_name ] = component_review_dict + if revision_approved_setting_changed: + message += 'Approved value <b>%s</b> saved for this revision.<br/>' % review.approved + if saved_component_names: + message += 'Reviews were saved for components: %s' % ', '.join( saved_component_names ) + if not revision_approved_setting_changed and not saved_component_names: + message += 'No changes were made to this review, so nothing was saved.' + if review and review.approved: + selected_value = review.approved + else: + selected_value = trans.model.ComponentReview.approved_states.NO + revision_approved_select_field = build_approved_select_field( trans, + name='revision_approved', + selected_value=selected_value, + for_component=False ) + rev, changeset_revision_label = get_rev_label_from_changeset_revision( repo, review.changeset_revision ) + return trans.fill_template( '/webapps/community/repository_review/edit_review.mako', + repository=repository, + review=review, + changeset_revision_label=changeset_revision_label, + revision_approved_select_field=revision_approved_select_field, + components_dict=components_dict, + message=message, + status=status ) + @web.expose + @web.require_login( "manage components" ) + def manage_components( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if operation == "create": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='create_component', + **kwd ) ) + elif operation == "edit": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='edit_component', + **kwd ) ) + if 'message' not in kwd: + message = "This is a list of repository components (features) that can be reviewed. You can add new components or change " + message += "the description of an existing component if appropriate. Click on the name link to change the description." + status = "warning" + kwd[ 'message' ] = message + kwd[ 'status' ] = status + return self.component_grid( trans, **kwd ) + @web.expose + @web.require_login( "manage repositories reviewed by me" ) + def manage_repositories_reviewed_by_me( self, trans, **kwd ): + # The value of the received id is the encoded repository id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + if 'operation' in kwd: + kwd[ 'mine' ] = True + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='manage_repositories_with_reviews', + **kwd ) ) + self.repositories_reviewed_by_me_grid.title = 'Repositories reviewed by me' + return self.repositories_reviewed_by_me_grid( trans, **kwd ) + @web.expose + @web.require_login( "manage repositories with reviews" ) + def manage_repositories_with_reviews( self, trans, **kwd ): + # The value of the received id is the encoded repository id. + if 'operation' in kwd: + operation = kwd['operation'].lower() + if operation == "inspect repository revisions": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='manage_repository_reviews', + **kwd ) ) + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='view_or_manage_repository', + **kwd ) ) + return self.repositories_with_reviews_grid( trans, **kwd ) + @web.expose + @web.require_login( "manage repositories without reviews" ) + def manage_repositories_without_reviews( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if operation == "inspect repository revisions": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='create_review', + **kwd ) ) + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository_review', + action='view_or_manage_repository', + **kwd ) ) + return self.repositories_without_reviews_grid( trans, **kwd ) + @web.expose + @web.require_login( "manage repository reviews" ) + def manage_repository_reviews( self, trans, mine=False, **kwd ): + # The value of the received id is the encoded repository id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + repository_id = kwd.get( 'id', None ) + if repository_id: + repository = get_repository( trans, repository_id ) + repo_dir = repository.repo_path + repo = hg.repository( get_configured_ui(), repo_dir ) + metadata_revision_hashes = [ metadata_revision.changeset_revision for metadata_revision in repository.metadata_revisions ] + reviewed_revision_hashes = [ reviewed_revisions.changeset_revision for reviewed_revisions in repository.reviewed_revisions ] + reviews_dict = odict() + for changeset in get_reversed_changelog_changesets( repo ): + ctx = repo.changectx( changeset ) + changeset_revision = str( ctx ) + if changeset_revision in metadata_revision_hashes or changeset_revision in reviewed_revision_hashes: + rev, changeset_revision_label = get_rev_label_from_changeset_revision( repo, changeset_revision ) + if changeset_revision in reviewed_revision_hashes: + # Find the review for this changeset_revision + repository_reviews = get_reviews_by_repository_id_changeset_revision( trans, repository_id, changeset_revision ) + # Determine if the current user can add a review to this revision. + can_add_review = trans.user not in [ repository_review.user for repository_review in repository_reviews ] + repository_metadata = get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision ) + repository_metadata_reviews = util.listify( repository_metadata.reviews ) + else: + repository_reviews = [] + repository_metadata_reviews = [] + can_add_review = True + installable = changeset_revision in metadata_revision_hashes + revision_dict = dict( changeset_revision_label=changeset_revision_label, + repository_reviews=repository_reviews, + repository_metadata_reviews=repository_metadata_reviews, + installable=installable, + can_add_review=can_add_review ) + reviews_dict[ changeset_revision ] = revision_dict + return trans.fill_template( '/webapps/community/repository_review/reviews_of_repository.mako', + repository=repository, + reviews_dict=reviews_dict, + mine=mine, + message=message, + status=status ) + @web.expose + @web.require_login( "manage repository reviews of revision" ) + def manage_repository_reviews_of_revision( self, trans, **kwd ): + # The value of the received id is the encoded repository id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + repository_id = kwd.get( 'id', None ) + changeset_revision = kwd.get( 'changeset_revision', None ) + repository = get_repository( trans, repository_id ) + repo_dir = repository.repo_path + repo = hg.repository( get_configured_ui(), repo_dir ) + installable = changeset_revision in [ metadata_revision.changeset_revision for metadata_revision in repository.metadata_revisions ] + rev, changeset_revision_label = get_rev_label_from_changeset_revision( repo, changeset_revision ) + reviews = get_reviews_by_repository_id_changeset_revision( trans, repository_id, changeset_revision ) + return trans.fill_template( '/webapps/community/repository_review/reviews_of_changeset_revision.mako', + repository=repository, + changeset_revision=changeset_revision, + changeset_revision_label=changeset_revision_label, + reviews=reviews, + installable=installable, + message=message, + status=status ) + @web.expose + @web.require_login( "repository reviews by user" ) + def repository_reviews_by_user( self, trans, **kwd ): + # The user may not be the current user. The value of the received id is the encoded user id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + user = get_user( trans, kwd[ 'id' ] ) + self.repository_reviews_by_user_grid.title = "All repository revision reviews for user '%s'" % user.username + return self.repository_reviews_by_user_grid( trans, **kwd ) + @web.expose + @web.require_login( "reviewed repositories i own" ) + def reviewed_repositories_i_own( self, trans, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + return self.reviewed_repositories_i_own_grid( trans, **kwd ) + @web.expose + @web.require_login( "select previous review" ) + def select_previous_review( self, trans, **kwd ): + # The value of the received id is the encoded repository id. + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + repository = get_repository( trans, kwd[ 'id' ] ) + changeset_revision = kwd.get( 'changeset_revision', None ) + repo = hg.repository( get_configured_ui(), repository.repo_path ) + previous_reviews_dict = get_previous_repository_reviews( trans, repository, changeset_revision ) + rev, changeset_revision_label = get_rev_label_from_changeset_revision( repo, changeset_revision ) + return trans.fill_template( '/webapps/community/repository_review/select_previous_review.mako', + repository=repository, + changeset_revision=changeset_revision, + changeset_revision_label=changeset_revision_label, + previous_reviews_dict=previous_reviews_dict, + message=message, + status=status ) + @web.expose + @web.require_login( "view or manage repository" ) + def view_or_manage_repository( self, trans, **kwd ): + repository = get_repository( trans, kwd[ 'id' ] ) + if trans.user_is_admin() or repository.user == trans.user: + return trans.response.send_redirect( web.url_for( controller='repository', + action='manage_repository', + cntrller='repository_review', + **kwd ) ) + else: + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_repository', + cntrller='repository_review', + **kwd ) ) + +# ----- Utility methods ----- + +def build_approved_select_field( trans, name, selected_value=None, for_component=True ): + options = [ ( 'No', trans.model.ComponentReview.approved_states.NO ), + ( 'Yes', trans.model.ComponentReview.approved_states.YES ) ] + if for_component: + options.append( ( 'Not applicable', trans.model.ComponentReview.approved_states.NA ) ) + select_field = SelectField( name=name ) + for option_tup in options: + selected = selected_value and option_tup[1] == selected_value + select_field.add_option( option_tup[0], option_tup[1], selected=selected ) + return select_field diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/controllers/upload.py --- a/lib/galaxy/webapps/community/controllers/upload.py +++ b/lib/galaxy/webapps/community/controllers/upload.py @@ -85,7 +85,6 @@ isgzip = False isbz2 = False if uploaded_file: - if uncompress_file: isgzip = is_gzip( uploaded_file_name ) if not isgzip: @@ -208,12 +207,10 @@ repo = hg.repository( get_configured_ui(), repo_dir ) undesirable_dirs_removed = 0 undesirable_files_removed = 0 - if upload_point is not None: full_path = os.path.abspath( os.path.join( repo_dir, upload_point ) ) else: full_path = os.path.abspath( repo_dir ) - filenames_in_archive = [] for root, dirs, files in os.walk( uploaded_directory ): for uploaded_file in files: diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/model/__init__.py --- a/lib/galaxy/webapps/community/model/__init__.py +++ b/lib/galaxy/webapps/community/model/__init__.py @@ -25,6 +25,13 @@ self.purged = False self.username = None self.new_repo_alert = False + def all_roles( self ): + roles = [ ura.role for ura in self.roles ] + for group in [ uga.group for uga in self.groups ]: + for role in [ gra.role for gra in group.roles ]: + if role not in roles: + roles.append( role ) + return roles def set_password_cleartext( self, cleartext ): """Set 'self.password' to the digest of 'cleartext'.""" self.password = new_secure_hash( text_type=cleartext ) @@ -173,7 +180,32 @@ self.tool_versions = tool_versions or dict() self.malicious = malicious self.downloadable = downloadable - + +class RepositoryReview( object ): + approved_states = Bunch( NO='no', YES='yes' ) + def __init__( self, repository_id=None, changeset_revision=None, user_id=None, rating=None, deleted=False ): + self.repository_id = repository_id + self.changeset_revision = changeset_revision + self.user_id = user_id + self.rating = rating + self.deleted = deleted + +class ComponentReview( object ): + approved_states = Bunch( NO='no', YES='yes', NA='not_applicable' ) + def __init__( self, repository_review_id=None, component_id=None, comment=None, private=False, approved=False, rating=None, deleted=False ): + self.repository_review_id = repository_review_id + self.component_id = component_id + self.comment = comment + self.private = private + self.approved = approved + self.rating = rating + self.deleted = deleted + +class Component( object ): + def __init__( self, name=None, description=None ): + self.name = name + self.description = description + class ItemRatingAssociation( object ): def __init__( self, id=None, user=None, item=None, rating=0, comment='' ): self.id = id diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/model/mapping.py --- a/lib/galaxy/webapps/community/model/mapping.py +++ b/lib/galaxy/webapps/community/model/mapping.py @@ -124,6 +124,34 @@ Column( "malicious", Boolean, default=False ), Column( "downloadable", Boolean, default=True ) ) +RepositoryReview.table = Table( "repository_review", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "repository_id", Integer, ForeignKey( "repository.id" ), index=True ), + Column( "changeset_revision", TrimmedString( 255 ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ), + Column( "approved", TrimmedString( 255 ) ), + Column( "rating", Integer, index=True ), + Column( "deleted", Boolean, index=True, default=False ) ) + +ComponentReview.table = Table( "component_review", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "repository_review_id", Integer, ForeignKey( "repository_review.id" ), index=True ), + Column( "component_id", Integer, ForeignKey( "component.id" ), index=True ), + Column( "comment", TEXT ), + Column( "private", Boolean, default=False ), + Column( "approved", TrimmedString( 255 ) ), + Column( "rating", Integer ), + Column( "deleted", Boolean, index=True, default=False ) ) + +Component.table = Table( "component", metadata, + Column( "id", Integer, primary_key=True ), + Column( "name", TrimmedString( 255 ) ), + Column( "description", TEXT ) ) + RepositoryRatingAssociation.table = Table( "repository_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), @@ -187,7 +215,7 @@ properties=dict( user=relation( User.mapper ) ) ) assign_mapper( context, Tag, Tag.table, - properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[Tag.table.c.id] ) ) ) ) + properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[ Tag.table.c.id ] ) ) ) ) assign_mapper( context, Category, Category.table, properties=dict( repositories=relation( RepositoryCategoryAssociation ) ) ) @@ -201,10 +229,47 @@ primaryjoin=( ( Repository.table.c.id == RepositoryMetadata.table.c.repository_id ) & ( RepositoryMetadata.table.c.downloadable == True ) ), order_by=desc( RepositoryMetadata.table.c.update_time ) ), metadata_revisions=relation( RepositoryMetadata, - order_by=desc( RepositoryMetadata.table.c.update_time ) ) ) ) + order_by=desc( RepositoryMetadata.table.c.update_time ) ), + reviews=relation( RepositoryReview, + primaryjoin=( ( Repository.table.c.id == RepositoryReview.table.c.repository_id ) ) ), + reviewers=relation( User, + secondary=RepositoryReview.table, + primaryjoin=( Repository.table.c.id == RepositoryReview.table.c.repository_id ), + secondaryjoin=( RepositoryReview.table.c.user_id == User.table.c.id ) ), + reviewed_revisions=relation( RepositoryMetadata, + secondary=RepositoryReview.table, + foreign_keys=[ RepositoryMetadata.table.c.repository_id, RepositoryMetadata.table.c.changeset_revision ], + primaryjoin=( Repository.table.c.id == RepositoryMetadata.table.c.repository_id ), + secondaryjoin=( ( RepositoryMetadata.table.c.repository_id == RepositoryReview.table.c.repository_id ) & ( RepositoryMetadata.table.c.changeset_revision == RepositoryReview.table.c.changeset_revision ) ) ) ) ) assign_mapper( context, RepositoryMetadata, RepositoryMetadata.table, - properties=dict( repository=relation( Repository ) ) ) + properties=dict( repository=relation( Repository ), + reviews=relation( RepositoryReview, + foreign_keys=[ RepositoryMetadata.table.c.repository_id, RepositoryMetadata.table.c.changeset_revision ], + primaryjoin=( ( RepositoryMetadata.table.c.repository_id == RepositoryReview.table.c.repository_id ) & ( RepositoryMetadata.table.c.changeset_revision == RepositoryReview.table.c.changeset_revision ) ) ) ) ) + +assign_mapper( context, RepositoryReview, RepositoryReview.table, + properties=dict( repository=relation( Repository, + primaryjoin=( RepositoryReview.table.c.repository_id == Repository.table.c.id ) ), + # Take case when using the mapper below! It should be used only when a new review is being created for a repository change set revision. + # Keep in mind that repository_metadata records can be removed from the database for certain change set revisions when metadata is being + # reset on a repository! + repository_metadata=relation( RepositoryMetadata, + foreign_keys=[ RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision ], + primaryjoin=( ( RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id ) & ( RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision ) ), + backref='review' ), + user=relation( User, backref="repository_reviews" ), + component_reviews=relation( ComponentReview, + primaryjoin=( ( RepositoryReview.table.c.id == ComponentReview.table.c.repository_review_id ) & ( ComponentReview.table.c.deleted == False ) ) ), + private_component_reviews=relation( ComponentReview, + primaryjoin=( ( RepositoryReview.table.c.id == ComponentReview.table.c.repository_review_id ) & ( ComponentReview.table.c.deleted == False ) & ( ComponentReview.table.c.private == True ) ) ) ) ) + +assign_mapper( context, ComponentReview, ComponentReview.table, + properties=dict( repository_review=relation( RepositoryReview ), + component=relation( Component, + primaryjoin=( ComponentReview.table.c.component_id == Component.table.c.id ) ) ) ) + +assign_mapper( context, Component, Component.table ) assign_mapper( context, RepositoryRatingAssociation, RepositoryRatingAssociation.table, properties=dict( repository=relation( Repository ), user=relation( User ) ) ) diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/model/migrate/versions/0013_add_review_tables.py --- /dev/null +++ b/lib/galaxy/webapps/community/model/migrate/versions/0013_add_review_tables.py @@ -0,0 +1,212 @@ +""" +Migration script to add the repository_review, component_review and component tables and the Repository Reviewer group and role. +""" +import datetime, logging, sys +from sqlalchemy import * +from sqlalchemy.orm import * +from migrate import * +from migrate.changeset import * +# Need our custom types, but don't import anything else from model +from galaxy.model.custom_types import * + +log = logging.getLogger( __name__ ) +log.setLevel(logging.DEBUG) +handler = logging.StreamHandler( sys.stdout ) +format = "%(name)s %(levelname)s %(asctime)s %(message)s" +formatter = logging.Formatter( format ) +handler.setFormatter( formatter ) +log.addHandler( handler ) + +metadata = MetaData( migrate_engine ) +db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) + +IUC = 'Intergalactic Utilities Commission' +NOW = datetime.datetime.utcnow +REVIEWER = 'Repository Reviewer' +ROLE_TYPE = 'system' + +def nextval( table, col='id' ): + if migrate_engine.name == 'postgres': + return "nextval('%s_%s_seq')" % ( table, col ) + elif migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + return "null" + else: + raise Exception( 'Unable to convert data for unknown database type: %s' % migrate_engine.name ) + +def localtimestamp(): + if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': + return "LOCALTIMESTAMP" + elif migrate_engine.name == 'sqlite': + return "current_date || ' ' || current_time" + else: + raise Exception( 'Unable to convert data for unknown database type: %s' % db ) + +def boolean_false(): + if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': + return False + elif migrate_engine.name == 'sqlite': + return 0 + else: + raise Exception( 'Unable to convert data for unknown database type: %s' % db ) + +RepositoryReview_table = Table( "repository_review", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=NOW ), + Column( "update_time", DateTime, default=NOW, onupdate=NOW ), + Column( "repository_id", Integer, ForeignKey( "repository.id" ), index=True ), + Column( "changeset_revision", TrimmedString( 255 ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ), + Column( "approved", TrimmedString( 255 ) ), + Column( "rating", Integer, index=True ), + Column( "deleted", Boolean, index=True, default=False ) ) + +ComponentReview_table = Table( "component_review", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=NOW ), + Column( "update_time", DateTime, default=NOW, onupdate=NOW ), + Column( "repository_review_id", Integer, ForeignKey( "repository_review.id" ), index=True ), + Column( "component_id", Integer, ForeignKey( "component.id" ), index=True ), + Column( "comment", TEXT ), + Column( "private", Boolean, default=False ), + Column( "approved", TrimmedString( 255 ) ), + Column( "rating", Integer ), + Column( "deleted", Boolean, index=True, default=False ) ) + +Component_table = Table( "component", metadata, + Column( "id", Integer, primary_key=True ), + Column( "name", TrimmedString( 255 ) ), + Column( "description", TEXT ) ) + +def upgrade(): + print __doc__ + metadata.reflect() + # Create new review tables. + try: + Component_table.create() + except Exception, e: + print str(e) + log.debug( "Creating component table failed: %s" % str( e ) ) + try: + RepositoryReview_table.create() + except Exception, e: + print str(e) + log.debug( "Creating repository_review table failed: %s" % str( e ) ) + try: + ComponentReview_table.create() + except Exception, e: + print str(e) + log.debug( "Creating component_review table failed: %s" % str( e ) ) + # Insert default Component values. + names = [ 'Data types', 'Functional tests', 'README', 'Tool dependencies', 'Tools', 'Workflows' ] + descriptions = [ 'Proprietary datatypes defined in a file named datatypes_conf.xml included in the repository', + 'Functional tests defined in each tool config included in the repository along with test data files', + 'An appropriately named file included in the repository that contains installation information or 3rd-party tool dependency licensing information', + 'Tool dependencies defined in a file named tool_dependencies.xml included in the repository for contained tools', + 'Galaxy tools included in the repository', + 'Exported Galaxy workflows included in the repository' ] + for tup in zip( names, descriptions ): + name, description = tup + cmd = "INSERT INTO component VALUES (" + cmd += "%s, " % nextval( 'component' ) + cmd += "'%s', " % name + cmd += "'%s' " % description + cmd += ");" + db_session.execute( cmd ) + # Insert a REVIEWER role into the role table. + cmd = "INSERT INTO role VALUES (" + cmd += "%s, " % nextval( 'role' ) + cmd += "%s, " % localtimestamp() + cmd += "%s, " % localtimestamp() + cmd += "'%s', " % REVIEWER + cmd += "'A user or group member with this role can review repositories.', " + cmd += "'%s', " % ROLE_TYPE + cmd += "%s" % boolean_false() + cmd += ");" + db_session.execute( cmd ) + # Get the id of the REVIEWER role. + cmd = "SELECT id FROM role WHERE name = '%s' and type = '%s';" % ( REVIEWER, ROLE_TYPE ) + row = db_session.execute( cmd ).fetchone() + if row: + role_id = row[ 0 ] + else: + role_id = None + # Insert an IUC group into the galaxy_group table. + cmd = "INSERT INTO galaxy_group VALUES (" + cmd += "%s, " % nextval( 'galaxy_group' ) + cmd += "%s, " % localtimestamp() + cmd += "%s, " % localtimestamp() + cmd += "'%s', " % IUC + cmd += "%s" % boolean_false() + cmd += ");" + db_session.execute( cmd ) + # Get the id of the IUC group. + cmd = "SELECT id FROM galaxy_group WHERE name = '%s';" % ( IUC ) + row = db_session.execute( cmd ).fetchone() + if row: + group_id = row[ 0 ] + else: + group_id = None + if group_id and role_id: + # Insert a group_role_association for the IUC group and the REVIEWER role. + cmd = "INSERT INTO group_role_association VALUES (" + cmd += "%s, " % nextval( 'group_role_association' ) + cmd += "%d, " % int( group_id ) + cmd += "%d, " % int( role_id ) + cmd += "%s, " % localtimestamp() + cmd += "%s " % localtimestamp() + cmd += ");" + db_session.execute( cmd ) + +def downgrade(): + metadata.reflect() + # Drop review tables. + try: + ComponentReview_table.drop() + except Exception, e: + print str(e) + log.debug( "Dropping component_review table failed: %s" % str( e ) ) + try: + RepositoryReview_table.drop() + except Exception, e: + print str(e) + log.debug( "Dropping repository_review table failed: %s" % str( e ) ) + try: + Component_table.drop() + except Exception, e: + print str(e) + log.debug( "Dropping component table failed: %s" % str( e ) ) + # Get the id of the REVIEWER group. + cmd = "SELECT id FROM galaxy_group WHERE name = '%s';" % ( IUC ) + row = db_session.execute( cmd ).fetchone() + if row: + group_id = row[ 0 ] + else: + group_id = None + # Get the id of the REVIEWER role. + cmd = "SELECT id FROM role WHERE name = '%s' and type = '%s';" % ( REVIEWER, ROLE_TYPE ) + row = db_session.execute( cmd ).fetchone() + if row: + role_id = row[ 0 ] + else: + role_id = None + # See if we have at least 1 user + cmd = "SELECT * FROM galaxy_user;" + users = db_session.execute( cmd ).fetchall() + if role_id: + if users: + # Delete all UserRoleAssociations for the REVIEWER role. + cmd = "DELETE FROM user_role_association WHERE role_id = %d;" % int( role_id ) + db_session.execute( cmd ) + if group_id: + # Delete all UserGroupAssociations for members of the IUC group. + cmd = "DELETE FROM user_group_association WHERE group_id = %d;" % int( group_id ) + db_session.execute( cmd ) + # Delete all GroupRoleAssociations for the IUC group and the REVIEWER role. + cmd = "DELETE FROM group_role_association WHERE group_id = %d and role_id = %d;" % ( int( group_id ), int( role_id ) ) + db_session.execute( cmd ) + # Delete the IUC group from the galaxy_group table. + cmd = "DELETE FROM galaxy_group WHERE id = %d;" % int( group_id ) + db_session.execute( cmd ) + # Delete the REVIEWER role from the role table. + cmd = "DELETE FROM role WHERE id = %d;" % int( role_id ) + db_session.execute( cmd ) diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 lib/galaxy/webapps/community/security/__init__.py --- a/lib/galaxy/webapps/community/security/__init__.py +++ b/lib/galaxy/webapps/community/security/__init__.py @@ -121,6 +121,11 @@ else: return None return role + def get_repository_reviewer_role( self ): + return self.sa_session.query( self.model.Role ) \ + .filter( and_( self.model.Role.table.c.name == 'Repository Reviewer', + self.model.Role.table.c.type == self.model.Role.types.SYSTEM ) ) \ + .first() def set_entity_group_associations( self, groups=[], users=[], roles=[], delete_existing_assocs=True ): for group in groups: if delete_existing_assocs: @@ -158,6 +163,13 @@ if user: return user.username in listify( repository.allow_push ) return False + def user_can_review_repositories( self, user ): + if user: + roles = user.all_roles() + if roles: + repository_reviewer_role = self.get_repository_reviewer_role() + return repository_reviewer_role and repository_reviewer_role in roles + return False def get_permitted_actions( filter=None ): '''Utility method to return a subset of RBACAgent's permitted actions''' diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/admin/tool_shed_repository/view_tool_metadata.mako --- a/templates/admin/tool_shed_repository/view_tool_metadata.mako +++ b/templates/admin/tool_shed_repository/view_tool_metadata.mako @@ -185,7 +185,6 @@ %> %if tests: <div class="form-row"> - <label>Functional tests:</label></td><table class="grid"><tr><td><b>name</b></td> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/admin/index.mako --- a/templates/webapps/community/admin/index.mako +++ b/templates/webapps/community/admin/index.mako @@ -36,6 +36,7 @@ </%def><%def name="left_panel()"> + <% can_review_repositories = trans.app.security_agent.user_can_review_repositories( trans.user ) %><div class="unified-panel-header" unselectable="on"><div class='unified-panel-header-inner'>Administration</div></div> @@ -64,6 +65,31 @@ </div></div></div> + %if can_review_repositories: + <div class="toolSectionPad"></div> + <div class="toolSectionTitle"> + Reviewing Repositories + </div> + <div class="toolSectionBody"> + <div class="toolSectionBg"> + %if trans.user.repository_reviews: + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_repositories_reviewed_by_me' )}">Repositories reviewed by me</a> + </div> + %endif + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_repositories_with_reviews' )}">All reviewed repositories</a> + </div> + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_repositories_without_reviews' )}">Repositories with no reviews</a> + </div> + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_components' )}">Manage review components</a> + </div> + </div> + </div> + %endif + <div class="toolSectionPad"></div><div class="toolSectionTitle"> Categories </div> @@ -91,12 +117,15 @@ </div></div></div> + <div class="toolSectionPad"></div><div class="toolSectionTitle"> Statistics </div> + <div class="toolSectionBody"><div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='regenerate_statistics' )}">View shed statistics</a></div> + </div></div></div></div> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/category/edit_category.mako --- a/templates/webapps/community/category/edit_category.mako +++ b/templates/webapps/community/category/edit_category.mako @@ -8,7 +8,7 @@ <div class="toolForm"><div class="toolFormTitle">Change category name and description</div><div class="toolFormBody"> - <form name="library" action="${h.url_for( controller='admin', action='edit_category' )}" method="post" > + <form name="edit_category" action="${h.url_for( controller='admin', action='edit_category' )}" method="post" ><div class="form-row"><label>Name:</label><div style="float: left; width: 250px; margin-right: 10px;"> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/index.mako --- a/templates/webapps/community/index.mako +++ b/templates/webapps/community/index.mako @@ -37,6 +37,7 @@ </%def><%def name="left_panel()"> + <% can_review_repositories = trans.app.security_agent.user_can_review_repositories( trans.user ) %><div class="unified-panel-header" unselectable="on"><div class='unified-panel-header-inner'>${trans.app.shed_counter.valid_tools} valid tools on ${trans.app.shed_counter.generation_time}</div></div> @@ -72,6 +73,11 @@ <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_repositories', operation='repositories_i_own' )}">Repositories I own</a></div> + %if has_reviewed_repositories: + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_repositories', operation='reviewed_repositories_i_own' )}">Reviewed repositories I own</a> + </div> + %endif <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_repositories', operation='writable_repositories' )}">My writable repositories</a></div> @@ -85,6 +91,30 @@ <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='repository', action='create_repository' )}">Create new repository</a></div> + %if can_review_repositories: + <div class="toolSectionPad"></div> + <div class="toolSectionTitle"> + Reviewing Repositories + </div> + <div class="toolSectionBody"> + <div class="toolSectionBg"> + %if trans.user.repository_reviews: + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_repositories_reviewed_by_me' )}">Repositories reviewed by me</a> + </div> + %endif + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_repositories_with_reviews' )}">All reviewed repositories</a> + </div> + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_repositories_without_reviews' )}">Repositories with no reviews</a> + </div> + <div class="toolTitle"> + <a target="galaxy_main" href="${h.url_for( controller='repository_review', action='manage_components' )}">Manage review components</a> + </div> + </div> + </div> + %endif %else: <div class="toolSectionPad"></div><div class="toolSectionTitle"> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository/manage_repository.mako --- a/templates/webapps/community/repository/manage_repository.mako +++ b/templates/webapps/community/repository/manage_repository.mako @@ -22,6 +22,12 @@ can_set_malicious = metadata and can_set_metadata and is_admin and changeset_revision == repository.tip can_reset_all_metadata = is_admin and len( repo ) > 0 has_readme = metadata and 'readme' in metadata + can_review_repository = trans.app.security_agent.user_can_review_repositories( trans.user ) + reviewing_repository = cntrller and cntrller == 'repository_review' + if changeset_revision == repository.tip: + tip_str = 'repository tip' + else: + tip_str = '' %><%! @@ -41,38 +47,53 @@ <br/><br/><ul class="manage-table-actions"> - %if is_new and can_upload: - <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> + %if reviewing_repository: + %if reviewed_by_user: + <a class="action-button" href="${h.url_for( controller='repository_review', action='edit_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Manage my review of this revision</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Add a review to this revision</a> + %endif %else: - <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> - <div popupmenu="repository-${repository.id}-popup"> - %if can_upload: - <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> - %endif - %if has_readme: - <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</a> - %endif - %if can_view_change_log: - <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.app.security.encode_id( repository.id ) )}">View change log</a> - %endif - %if can_rate: - <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.app.security.encode_id( repository.id ) )}">Rate repository</a> - %endif - %if can_browse_contents: - <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.app.security.encode_id( repository.id ) )}">${browse_label}</a> - %endif - %if can_contact_owner: - <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ) )}">Contact repository owner</a> - %endif - %if can_reset_all_metadata: - <a class="action-button" href="${h.url_for( controller='repository', action='reset_all_metadata', id=trans.security.encode_id( repository.id ) )}">Reset all repository metadata</a> - %endif - %if can_download: - <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='gz' )}">Download as a .tar.gz file</a> - <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='bz2' )}">Download as a .tar.bz2 file</a> - <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='zip' )}">Download as a zip file</a> - %endif - </div> + %if is_new and can_upload: + <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> + %else: + <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> + <div popupmenu="repository-${repository.id}-popup"> + %if can_review_repository: + %if reviewed_by_user: + <a class="action-button" href="${h.url_for( controller='repository_review', action='edit_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Manage my review of this revision</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Add a review to this revision</a> + %endif + %endif + %if can_upload: + <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> + %endif + %if has_readme: + <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</a> + %endif + %if can_view_change_log: + <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.app.security.encode_id( repository.id ) )}">View change log</a> + %endif + %if can_rate: + <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.app.security.encode_id( repository.id ) )}">Rate repository</a> + %endif + %if can_browse_contents: + <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.app.security.encode_id( repository.id ) )}">${browse_label}</a> + %endif + %if can_contact_owner: + <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ) )}">Contact repository owner</a> + %endif + %if can_reset_all_metadata: + <a class="action-button" href="${h.url_for( controller='repository', action='reset_all_metadata', id=trans.security.encode_id( repository.id ) )}">Reset all repository metadata</a> + %endif + %if can_download: + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='gz' )}">Download as a .tar.gz file</a> + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='bz2' )}">Download as a .tar.bz2 file</a> + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='zip' )}">Download as a zip file</a> + %endif + </div> + %endif %endif </ul> @@ -86,15 +107,13 @@ <div class="toolFormBody"><form name="change_revision" id="change_revision" action="${h.url_for( controller='repository', action='manage_repository', id=trans.security.encode_id( repository.id ) )}" method="post" ><div class="form-row"> - <% - if changeset_revision == repository.tip: - tip_str = 'repository tip' - else: - tip_str = '' - %> ${changeset_revision_select_field.get_html()} <i>${tip_str}</i><div class="toolParamHelp" style="clear: both;"> - Select a revision to inspect and download versions of tools from this repository. + %if reviewing_repository or can_review_repository: + Select a revision to inspect for adding or managing a review or for download or installation. + %else: + Select a revision to inspect for download or installation. + %endif </div></div></form> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository/view_repository.mako --- a/templates/webapps/community/repository/view_repository.mako +++ b/templates/webapps/community/repository/view_repository.mako @@ -18,6 +18,8 @@ else: browse_label = 'Browse repository tip files' has_readme = metadata and 'readme' in metadata + reviewing_repository = cntrller and cntrller == 'repository_review' + can_review_repository = trans.app.security_agent.user_can_review_repositories( trans.user ) %><%! @@ -38,35 +40,50 @@ <br/><br/><ul class="manage-table-actions"> %if trans.webapp.name == 'community': - %if is_new and can_upload: - <li><a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a></li> + %if reviewing_repository: + %if reviewed_by_user: + <a class="action-button" href="${h.url_for( controller='repository_review', action='edit_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Manage my review of this revision</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Add a review to this revision</a> + %endif %else: - <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> - <div popupmenu="repository-${repository.id}-popup"> - %if can_upload: - <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> - %endif - %if has_readme: - <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</a> - %endif - %if can_view_change_log: - <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.app.security.encode_id( repository.id ) )}">View change log</a> - %endif - %if can_rate: - <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.app.security.encode_id( repository.id ) )}">Rate repository</a> - %endif - %if can_browse_contents: - <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.app.security.encode_id( repository.id ) )}">${browse_label}</a> - %endif - %if can_contact_owner: - <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ) )}">Contact repository owner</a> - %endif - %if can_download: - <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='gz' )}">Download as a .tar.gz file</a> - <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='bz2' )}">Download as a .tar.bz2 file</a> - <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='zip' )}">Download as a zip file</a> - %endif - </div> + %if is_new and can_upload: + <li><a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a></li> + %else: + <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> + <div popupmenu="repository-${repository.id}-popup"> + %if can_review_repository: + %if reviewed_by_user: + <a class="action-button" href="${h.url_for( controller='repository_review', action='edit_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Manage my review of this revision</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Add a review to this revision</a> + %endif + %endif + %if can_upload: + <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> + %endif + %if has_readme: + <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</a> + %endif + %if can_view_change_log: + <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.app.security.encode_id( repository.id ) )}">View change log</a> + %endif + %if can_rate: + <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.app.security.encode_id( repository.id ) )}">Rate repository</a> + %endif + %if can_browse_contents: + <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.app.security.encode_id( repository.id ) )}">${browse_label}</a> + %endif + %if can_contact_owner: + <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ) )}">Contact repository owner</a> + %endif + %if can_download: + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='gz' )}">Download as a .tar.gz file</a> + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='bz2' )}">Download as a .tar.bz2 file</a> + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='zip' )}">Download as a zip file</a> + %endif + </div> + %endif %endif %else: <li><a class="action-button" href="${h.url_for( controller='repository', action='install_repositories_by_revision', repository_ids=trans.security.encode_id( repository.id ), changeset_revisions=changeset_revision )}">Install to local Galaxy</a></li> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository/view_tool_metadata.mako --- a/templates/webapps/community/repository/view_tool_metadata.mako +++ b/templates/webapps/community/repository/view_tool_metadata.mako @@ -21,6 +21,7 @@ else: browse_label = 'Browse repository tip files' has_readme = metadata and 'readme' in metadata + can_review_repository = trans.app.security_agent.user_can_review_repositories( trans.user ) %><%! @@ -52,6 +53,13 @@ %else: <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li><div popupmenu="repository-${repository.id}-popup"> + %if can_review_repository: + %if reviewed_by_user: + <a class="action-button" href="${h.url_for( controller='repository_review', action='edit_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Manage my review of this revision</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Add a review to this revision</a> + %endif + %endif %if can_manage: <a class="action-button" href="${h.url_for( controller='repository', action='manage_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Manage repository</a> %else: @@ -274,7 +282,6 @@ %> %if tests: <div class="form-row"> - <label>Functional tests:</label></td><table class="grid"><tr><td><b>name</b></td> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/browse_review.mako --- /dev/null +++ b/templates/webapps/community/repository_review/browse_review.mako @@ -0,0 +1,125 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/community/common/common.mako" import="*" /> + +<% + from galaxy.web.form_builder import CheckboxField + from galaxy.webapps.community.controllers.common import STRSEP + can_manage_repository = is_admin or repository.user == trans.user +%> + +<%def name="stylesheets()"> + ${h.css('base','panel_layout','jquery.rating')} +</%def> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js( "libs/jquery/jquery.rating" )} +</%def> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" id="review-${review.id}-popup" class="menubutton">Review Actions</a></li> + <div popupmenu="review-${review.id}-popup"> + %if can_manage_repository: + <a class="action-button" href="${h.url_for( controller='repository', action='manage_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=review.changeset_revision )}">Manage repository</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository', action='view_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=review.changeset_revision )}">View repository</a> + %endif + </div> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Review of repository '${repository.name}'</div> + <div class="toolFormBody"> + <div class="form-row"> + <label>Reviewer:</label> + ${review.user.username} + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Repository revision:</label> + <a class="action-button" href="${h.url_for( controller='repository_review', action='view_or_manage_repository', id=trans.security.encode_id( repository.id ), changeset_revision=review.changeset_revision )}">${changeset_revision_label}</a> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Repository owner:</label> + ${repository.user.username} + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Repository synopsis:</label> + ${repository.description} + <div style="clear: both"></div> + </div> + <div class="form-row"> + %if review.component_reviews: + <table class="grid"> + %for component_review in review.component_reviews: + <% + component = component_review.component + + # Initialize Private check box. + private_check_box_name = '%s%sprivate' % ( component.name, STRSEP ) + private_check_box = CheckboxField( name=private_check_box_name, checked=component_review.private ) + + # Initialize star rating. + rating_name = '%s%srating' % ( component.name, STRSEP ) + + review_comment = component_review.comment.replace( '\n', '<br/>' ) + %> + <tr> + <td bgcolor="#D8D8D8"><b>${component.name}</b></td> + <td bgcolor="#D8D8D8">${component.description}</td> + </tr> + <tr> + <td colspan="2"> + <table class="grid"> + <tr> + <td> + <label>Private:</label> + ${private_check_box.get_html( disabled=True )} + <div class="toolParamHelp" style="clear: both;"> + A private review can be accessed only by the owner of the repository and the IUC. + </div> + <div style="clear: both"></div> + </td> + </tr> + %if component_review.comment: + <tr> + <td> + <div overflow-wrap:normal;overflow:hidden;word-break:keep-all;word-wrap:break-word;line-break:strict;> + ${review_comment} + </div> + </td> + </tr> + %endif + <tr> + <td> + <label>Approved:</label> + ${component_review.approved} + <div style="clear: both"></div> + </td> + </tr> + <tr> + <td> + <label>Rating:</label> + ${render_star_rating( rating_name, component_review.rating, disabled=True )} + <div style="clear: both"></div> + </td> + </tr> + </table> + </td> + </tr> + %endfor + </table> + %else: + This review has not yet been started. + %endif + </div> + </div> +</div> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/create_component.mako --- /dev/null +++ b/templates/webapps/community/repository_review/create_component.mako @@ -0,0 +1,34 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +<%def name="javascripts()"> + ${parent.javascripts()} + <script type="text/javascript"> + $(function(){ + $("input:text:first").focus(); + }) + </script> +</%def> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Create Component</div> + <div class="toolFormBody"> + <form name="create_component" id="create_component" action="${h.url_for( controller='repository_review', action='create_component' )}" method="post" > + <div class="form-row"> + <label>Name:</label> + <input name="name" type="textfield" value="${name}" size=40"/> + </div> + <div class="form-row"> + <label>Description:</label> + <input name="description" type="textfield" value="${description}" size=40"/> + </div> + <div class="form-row"> + <input type="submit" name="create_component_button" value="Save"/> + </div> + </form> + </div> +</div> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/edit_component.mako --- /dev/null +++ b/templates/webapps/community/repository_review/edit_component.mako @@ -0,0 +1,37 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Change component description</div> + <div class="toolFormBody"> + <form name="edit_component" action="${h.url_for( controller='repository_review', action='edit_component' )}" method="post" > + <div class="form-row"> + <label>Name:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + ${component.name} + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Description:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + <input name="description" type="textfield" value="${component.description}" size=40"/> + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <div style="float: left; width: 250px; margin-right: 10px;"> + <input type="hidden" name="id" value="${trans.security.encode_id( component.id )}"/> + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <input type="submit" name="edit_component_button" value="Save"/> + </div> + </form> + </div> +</div> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/edit_review.mako --- /dev/null +++ b/templates/webapps/community/repository_review/edit_review.mako @@ -0,0 +1,173 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/community/common/common.mako" import="*" /> + +<% + from galaxy.web.form_builder import CheckboxField + from galaxy.webapps.community.controllers.repository_review import build_approved_select_field + from galaxy.webapps.community.controllers.common import STRSEP + can_manage_repository = is_admin or repository.user == trans.user +%> + +<%def name="stylesheets()"> + ${h.css('base','panel_layout','jquery.rating')} +</%def> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js( "libs/jquery/jquery.rating" )} +</%def> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" id="review-${review.id}-popup" class="menubutton">Review Actions</a></li> + <div popupmenu="review-${review.id}-popup"> + %if can_manage_repository: + <a class="action-button" href="${h.url_for( controller='repository', action='manage_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=review.changeset_revision )}">Manage repository</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository', action='view_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=review.changeset_revision )}">View repository</a> + %endif + </div> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">My review of repository '${repository.name}'</div> + <div class="toolFormBody"> + <form name="edit_review" action="${h.url_for( controller='repository_review', action='edit_review', id=trans.security.encode_id( review.id ) )}" method="post" > + <div class="form-row"> + <label>Repository revision:</label> + <a class="action-button" href="${h.url_for( controller='repository_review', action='view_or_manage_repository', id=trans.security.encode_id( repository.id ), changeset_revision=review.changeset_revision )}">${changeset_revision_label}</a> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Repository owner:</label> + ${repository.user.username} + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Repository synopsis:</label> + ${repository.description} + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Approve this repository revision?</label> + ${revision_approved_select_field.get_html()} + <div class="toolParamHelp" style="clear: both;"> + Individual components below may be approved without approving the repository revision. + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <input type="submit" name="revision_approved_button" value="Save"/> + <div class="toolParamHelp" style="clear: both;"> + All changes made on this page will be saved when any <b>Save</b> button is clicked. + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <table class="grid"> + %for component_name, component_review_dict in components_dict.items(): + <% + component = component_review_dict[ 'component' ] + encoded_component_id = trans.security.encode_id( component.id ) + + component_review = component_review_dict[ 'component_review' ] + if component_review: + comment = component_review.comment or '' + rating = component_review.rating + approved_select_field_selected_value = component_review.approved + private = component_review.private + else: + comment = '' + rating = 0 + approved_select_field_selected_value = None + private = False + + # Initialize Approved select field. + approved_select_field_name = '%s%sapproved' % ( component_name, STRSEP ) + approved_select_field = build_approved_select_field( trans, name=approved_select_field_name, selected_value=approved_select_field_selected_value, for_component=True ) + + # Initialize Private check box. + private_check_box_name = '%s%sprivate' % ( component_name, STRSEP ) + private_check_box = CheckboxField( name=private_check_box_name, checked=private ) + + # Initialize star rating. + rating_name = '%s%srating' % ( component_name, STRSEP ) + + # Initialize comment text area. + comment_name = '%s%scomment' % ( component_name, STRSEP ) + + # Initialize the component id form field name. + component_id_name = '%s%scomponent_id' % ( component_name, STRSEP ) + + # Initialize the Save button. + review_button_name = '%s%sreview_button' % ( component_name, STRSEP ) + %> + <tr> + <td bgcolor="#D8D8D8"><b>${component.name}</b></td> + <td bgcolor="#D8D8D8">${component.description}</td> + </tr> + <tr> + <td colspan="2"> + <table class="grid"> + <tr> + <td> + <label>Mark private:</label> + ${private_check_box.get_html()} + <div class="toolParamHelp" style="clear: both;"> + A private review can be accessed only by the owner of the repository and the IUC. + </div> + <div style="clear: both"></div> + </td> + </tr> + <tr> + <td> + <label>Comments:</label> + %if component_review: + <pre><textarea name="${comment_name}" rows="3" cols="80">${comment}</textarea></pre> + %else: + <textarea name="${comment_name}" rows="3" cols="80"></textarea> + %endif + <div style="clear: both"></div> + </td> + </tr> + <tr> + <td> + <label>Approved:</label> + ${approved_select_field.get_html()} + <div style="clear: both"></div> + </td> + </tr> + <tr> + <td> + <label>Rating:</label> + ${render_star_rating( rating_name, rating )} + <div style="clear: both"></div> + <div class="toolParamHelp" style="clear: both;"> + Rate this component only - the average of all component ratings defines the value of the repository rating. + </div> + </td> + </tr> + <tr> + <td> + <input type="hidden" name="${component_id_name}" value="${encoded_component_id}"/> + <input type="submit" name="${review_button_name}" value="Save"/> + <div style="clear: both"></div> + <div class="toolParamHelp" style="clear: both;"> + All changes made on this page will be saved when any <b>Save</b> button is clicked. + </div> + </td> + </tr> + </table> + </td> + </tr> + %endfor + </table> + </div> + </form> + </div> +</div> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/grid.mako --- /dev/null +++ b/templates/webapps/community/repository_review/grid.mako @@ -0,0 +1,1 @@ +<%inherit file="/grid_base.mako"/> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/reviews_of_changeset_revision.mako --- /dev/null +++ b/templates/webapps/community/repository_review/reviews_of_changeset_revision.mako @@ -0,0 +1,145 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/community/common/common.mako" import="*" /> +<%namespace file="/webapps/community/repository/common.mako" import="*" /> + +<% + from galaxy.webapps.community.controllers.repository_review import build_approved_select_field + from galaxy.webapps.community.controllers.common import STRSEP + is_admin = trans.user_is_admin() + is_new = repository.is_new + can_browse_contents = not is_new + can_contact_owner = trans.user and trans.user != repository.user + can_manage = is_admin or repository.user == trans.user + can_push = trans.app.security_agent.can_push( trans.user, repository ) + can_rate = not is_new and trans.user and repository.user != trans.user + can_view_change_log = not is_new + if can_push: + browse_label = 'Browse or delete repository tip files' + else: + browse_label = 'Browse repository tip files' + if installable: + installable_str = 'yes' + else: + installable_str = 'no' + can_review_repositories = trans.app.security_agent.user_can_review_repositories( trans.user ) +%> + +<%! + def inherit(context): + if context.get('use_panels'): + return '/webapps/community/base_panels.mako' + else: + return '/base.mako' +%> +<%inherit file="${inherit(context)}"/> + +<%def name="stylesheets()"> + ${h.css('base','panel_layout','jquery.rating')} +</%def> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js( "libs/jquery/jquery.rating" )} + ${common_javascripts(repository)} +</%def> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> + <div popupmenu="repository-${repository.id}-popup"> + %if can_manage: + <a class="action-button" href="${h.url_for( controller='repository', action='manage_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Manage repository</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository', action='view_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View repository</a> + %endif + %if can_view_change_log: + <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.security.encode_id( repository.id ) )}">View change log</a> + %endif + %if can_rate: + <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.security.encode_id( repository.id ) )}">Rate repository</a> + %endif + %if can_browse_contents: + <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">${browse_label}</a> + %endif + %if can_contact_owner: + <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ) )}">Contact repository owner</a> + %endif + </div> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Revision reviews of repository '${repository.name}'</div> + <div class="toolFormBody"> + <div class="form-row"> + <label>Revision:</label> + <a class="action-button" href="${h.url_for( controller='repository_review', action='view_or_manage_repository', id=trans.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">${changeset_revision_label}</a> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Revision is installable:</label> + ${installable_str} + <div style="clear: both"></div> + </div> + <div class="form-row"> + %if reviews: + <table class="grid"> + <tr> + <th>Reviewer</th> + <th>Repository rating</th> + <th>Approved</th> + <th></th> + </tr> + %for review in reviews: + <% + encoded_review_id = trans.security.encode_id( review.id ) + approved_select_field_name = '%s%sapproved' % ( encoded_review_id, STRSEP ) + approved_select_field_selected_value = review.approved + approved_select_field = build_approved_select_field( trans, name=approved_select_field_name, selected_value=approved_select_field_selected_value, for_component=False ) + if review.approved not in [ None, 'None', 'none' ]: + approved_str = review.approved + else: + approved_str = '' + repository_rating_name = '%srepository_rating' % encoded_review_id + %> + <tr> + <td> + <div style="float:left;" class="menubutton split popup" id="${encoded_review_id}-popup"> + <a class="view-info" href="${h.url_for( controller='repository_review', action='repository_reviews_by_user', id=trans.security.encode_id( review.user.id ) )}">${review.user.username}</a> + </div> + <div popupmenu="${encoded_review_id}-popup"> + %if review.user == trans.user: + <a class="action-button" href="${h.url_for( controller='repository_review', action='edit_review', id=encoded_review_id )}">Edit my review</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository_review', action='browse_review', id=encoded_review_id )}">Browse this review</a> + %endif + </div> + </td> + <td>${render_star_rating( repository_rating_name, review.rating, disabled=True )}</td> + %if review.user == trans.user: + <form name="approve_repository_review" action="${h.url_for( controller='repository_review', action='approve_repository_review', id=encoded_review_id ) }" method="post" > + <td>${approved_select_field.get_html()}</td> + <td><input type="submit" name="approve_repository_review_button" value="Save"/></td> + </form> + %else: + <td>${approved_str}</td> + <td></td> + %endif + </tr> + %endfor + </table> + %else: + <label>This repository revision has not yet been reviewed:</label> + %if can_review_repositories: + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Add a review to this revision</a> + <div style="clear: both"></div> + %endif + %endif + </div> + <div style="clear: both"></div> + </div> +</div> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/reviews_of_repository.mako --- /dev/null +++ b/templates/webapps/community/repository_review/reviews_of_repository.mako @@ -0,0 +1,123 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/community/common/common.mako" import="*" /> +<%namespace file="/webapps/community/repository/common.mako" import="*" /> + +<% + is_admin = trans.user_is_admin() + is_new = repository.is_new + can_browse_contents = not is_new + can_contact_owner = trans.user and trans.user != repository.user + can_manage = is_admin or repository.user == trans.user + can_push = trans.app.security_agent.can_push( trans.user, repository ) + can_rate = not is_new and trans.user and repository.user != trans.user + can_view_change_log = not is_new + if can_push: + browse_label = 'Browse or delete repository tip files' + else: + browse_label = 'Browse repository tip files' + if mine: + title = "My reviews of repository '%s'" % repository.name + review_revision_label = "Manage my review of this revision" + else: + title = "All reviews of repository '%s'" % repository.name + review_revision_label = "Inspect reviews of this revision" +%> + +<%! + def inherit(context): + if context.get('use_panels'): + return '/webapps/community/base_panels.mako' + else: + return '/base.mako' +%> +<%inherit file="${inherit(context)}"/> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js( "libs/jquery/jquery.rating" )} + ${common_javascripts(repository)} +</%def> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> + <div popupmenu="repository-${repository.id}-popup"> + %if can_manage: + <a class="action-button" href="${h.url_for( controller='repository', action='manage_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip )}">Manage repository</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository', action='view_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip )}">View repository</a> + %endif + %if can_view_change_log: + <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.security.encode_id( repository.id ) )}">View change log</a> + %endif + %if can_rate: + <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.security.encode_id( repository.id ) )}">Rate repository</a> + %endif + %if can_browse_contents: + <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">${browse_label}</a> + %endif + %if can_contact_owner: + <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ) )}">Contact repository owner</a> + %endif + </div> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">${title}</div> + <div class="toolFormBody"> + <div class="form-row"> + <table class="grid"> + <tr> + <th>Revision</th> + <th>Reviewers</th> + <th>Installable</th> + </tr> + %for changeset_revision, revision_dict in reviews_dict.items(): + <% + changeset_revision_label = revision_dict[ 'changeset_revision_label' ] + repository_reviews = revision_dict[ 'repository_reviews' ] + repository_metadata_reviews = revision_dict[ 'repository_metadata_reviews' ] + reviewers_str = '' + if repository_reviews: + for repository_review in repository_reviews: + reviewers_str += '<a class="view-info" href="' + if repository_review.user == trans.user: + reviewers_str += 'edit_review' + else: + reviewers_str += 'browse_review' + reviewers_str += '?id=%s">%s</a>' % ( trans.security.encode_id( repository_review.id ), repository_review.user.username ) + reviewers_str += ' | ' + reviewers_str = reviewers_str.rstrip( '| ' ) + if revision_dict[ 'installable' ]: + installable_str = 'yes' + else: + installable_str = '' + can_add_review = revision_dict[ 'can_add_review' ] + %> + <tr> + <td> + <div style="float:left;" class="menubutton split popup" id="${changeset_revision}-popup"> + <a class="view-info" href="${h.url_for( controller='repository_review', action='view_or_manage_repository', id=trans.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">${changeset_revision_label}</a> + </div> + <div popupmenu="${changeset_revision}-popup"> + %if repository_reviews: + <a class="action-button" href="${h.url_for( controller='repository_review', action='manage_repository_reviews_of_revision', id=trans.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Browse reviews of this revision</a> + %elif can_add_review: + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Add a review to this revision</a> + %endif + </div> + </td> + <td>${reviewers_str}</td> + <td>${installable_str}</td> + </tr> + %endfor + </table> + </div> + <div style="clear: both"></div> + </div> +</div> diff -r 12fcd068b12eb844d7eded11bd70826c4275021c -r 0698fc666bd04a28b88f1472ac8eeb8dfb3a6423 templates/webapps/community/repository_review/select_previous_review.mako --- /dev/null +++ b/templates/webapps/community/repository_review/select_previous_review.mako @@ -0,0 +1,127 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/community/common/common.mako" import="*" /> +<%namespace file="/webapps/community/repository/common.mako" import="*" /> + +<% + is_admin = trans.user_is_admin() + is_new = repository.is_new + can_browse_contents = not is_new + can_contact_owner = trans.user and trans.user != repository.user + can_manage = is_admin or repository.user == trans.user + can_push = trans.app.security_agent.can_push( trans.user, repository ) + can_rate = not is_new and trans.user and repository.user != trans.user + can_view_change_log = not is_new + if can_push: + browse_label = 'Browse or delete repository tip files' + else: + browse_label = 'Browse repository tip files' + can_review_repositories = trans.app.security_agent.user_can_review_repositories( trans.user ) +%> + +<%! + def inherit(context): + if context.get('use_panels'): + return '/webapps/community/base_panels.mako' + else: + return '/base.mako' +%> +<%inherit file="${inherit(context)}"/> + +<%def name="stylesheets()"> + ${h.css('base','panel_layout','jquery.rating')} +</%def> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js( "libs/jquery/jquery.rating" )} + ${common_javascripts(repository)} +</%def> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> + <div popupmenu="repository-${repository.id}-popup"> + %if can_manage: + <a class="action-button" href="${h.url_for( controller='repository', action='manage_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip )}">Manage repository</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository', action='view_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip )}">View repository</a> + %endif + %if can_view_change_log: + <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.security.encode_id( repository.id ) )}">View change log</a> + %endif + %if can_rate: + <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.security.encode_id( repository.id ) )}">Rate repository</a> + %endif + %if can_browse_contents: + <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">${browse_label}</a> + %endif + %if can_contact_owner: + <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ) )}">Contact repository owner</a> + %endif + </div> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="warningmessage"> + You have elected to create a new review for revision <b>${changeset_revision_label}</b>of this repository. Since previous revisions have been reviewed, + you can select a previous review to copy to your new review, or click the <b>Create a review without copying</b> button. +</div> + +<div class="toolForm"> + <div class="toolFormTitle">Select previous revision review of repository '${repository.name}'</div> + <div class="toolFormBody"> + <div class="form-row"> + <label>Revision for new review:</label> + <a class="action-button" href="${h.url_for( controller='repository_review', action='view_or_manage_repository', id=trans.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">${changeset_revision_label}</a> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <table class="grid"> + <tr> + </tr> + <td bgcolor="#D8D8D8" colspan="4"><b>Previous revision reviews of repository '${repository.name}' that can be copied to your new review</b></td> + <tr> + <th>Reviewer</th> + <th>Revision reviewed</th> + <th>Repository rating</th> + <th>Approved</th> + </tr> + %for previous_changeset_revision, previous_changeset_revision_dict in previous_reviews_dict.items(): + <% + previous_changeset_revision_label = previous_changeset_revision_dict[ 'changeset_revision_label' ] + previous_reviews = previous_changeset_revision_dict[ 'reviews' ] + %> + %for review in previous_reviews: + <% + encoded_review_id = trans.security.encode_id( review.id ) + if review.approved not in [ None, 'None', 'none' ]: + approved_str = review.approved + else: + approved_str = '' + repository_rating_name = '%srepository_rating' % encoded_review_id + %> + <tr> + <td> + <div style="float:left;" class="menubutton split popup" id="${encoded_review_id}-popup"> + <a class="view-info" href="${h.url_for( controller='repository_review', action='browse_review', id=encoded_review_id )}">${review.user.username}</a> + </div> + <div popupmenu="${encoded_review_id}-popup"> + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.security.encode_id( repository.id ), changeset_revision=changeset_revision, previous_review_id=encoded_review_id )}">Copy this review</a> + </div> + </td> + <td>${previous_changeset_revision_label}</td> + <td>${render_star_rating( repository_rating_name, review.rating, disabled=True )}</td> + <td>${approved_str}</td> + </tr> + %endfor + %endfor + </table> + </div> + <div style="clear: both"></div> + <a class="action-button" href="${h.url_for( controller='repository_review', action='create_review', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, create_without_copying=True )}">Create a review without copying</a> + </div> +</div> 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.