# HG changeset patch # User Marcin Kuzminski # Date 2020-04-17 20:08:42 # Node ID 9d74b996ed90ae7e1cd4e6c88fe2842bc1e63071 # Parent 4bf03e9f08c33abb54b985587a03a2d456823189 events: added support for pull-request-comment and commit-comment events. - allows chopts like actions on commits - re organized events and code cleanups diff --git a/rhodecode/events/__init__.py b/rhodecode/events/__init__.py --- a/rhodecode/events/__init__.py +++ b/rhodecode/events/__init__.py @@ -75,4 +75,5 @@ from rhodecode.events.pullrequest import PullRequestReviewEvent, PullRequestMergeEvent, PullRequestCloseEvent, + PullRequestCommentEvent, ) diff --git a/rhodecode/events/base.py b/rhodecode/events/base.py --- a/rhodecode/events/base.py +++ b/rhodecode/events/base.py @@ -44,6 +44,9 @@ class RhodecodeEvent(object): self._request = request self.utc_timestamp = datetime.datetime.utcnow() + def __repr__(self): + return '<%s:(%s)>' % (self.__class__.__name__, self.name) + def get_request(self): if self._request: return self._request @@ -116,3 +119,4 @@ class RhodeCodeIntegrationEvent(Rhodecod """ Special subclass for Integration events """ + description = '' diff --git a/rhodecode/events/pullrequest.py b/rhodecode/events/pullrequest.py --- a/rhodecode/events/pullrequest.py +++ b/rhodecode/events/pullrequest.py @@ -77,6 +77,7 @@ class PullRequestCreateEvent(PullRequest """ name = 'pullrequest-create' display_name = lazy_ugettext('pullrequest created') + description = lazy_ugettext('Event triggered after pull request was created') class PullRequestCloseEvent(PullRequestEvent): @@ -86,6 +87,7 @@ class PullRequestCloseEvent(PullRequestE """ name = 'pullrequest-close' display_name = lazy_ugettext('pullrequest closed') + description = lazy_ugettext('Event triggered after pull request was closed') class PullRequestUpdateEvent(PullRequestEvent): @@ -95,6 +97,7 @@ class PullRequestUpdateEvent(PullRequest """ name = 'pullrequest-update' display_name = lazy_ugettext('pullrequest commits updated') + description = lazy_ugettext('Event triggered after pull requests was updated') class PullRequestReviewEvent(PullRequestEvent): @@ -104,6 +107,8 @@ class PullRequestReviewEvent(PullRequest """ name = 'pullrequest-review' display_name = lazy_ugettext('pullrequest review changed') + description = lazy_ugettext('Event triggered after a review status of a ' + 'pull requests has changed to other.') def __init__(self, pullrequest, status): super(PullRequestReviewEvent, self).__init__(pullrequest) @@ -117,6 +122,8 @@ class PullRequestMergeEvent(PullRequestE """ name = 'pullrequest-merge' display_name = lazy_ugettext('pullrequest merged') + description = lazy_ugettext('Event triggered after a successful merge operation ' + 'was executed on a pull request') class PullRequestCommentEvent(PullRequestEvent): @@ -126,6 +133,8 @@ class PullRequestCommentEvent(PullReques """ name = 'pullrequest-comment' display_name = lazy_ugettext('pullrequest commented') + description = lazy_ugettext('Event triggered after a comment was made on a code ' + 'in the pull request') def __init__(self, pullrequest, comment): super(PullRequestCommentEvent, self).__init__(pullrequest) diff --git a/rhodecode/events/repo.py b/rhodecode/events/repo.py --- a/rhodecode/events/repo.py +++ b/rhodecode/events/repo.py @@ -186,13 +186,33 @@ class RepoCommitCommentEvent(RepoEvent): An instance of this class is emitted as an :term:`event` after a comment is made on repository commit. """ + + name = 'repo-commit-comment' + display_name = lazy_ugettext('repository commit comment') + description = lazy_ugettext('Event triggered after a comment was made ' + 'on commit inside a repository') + def __init__(self, repo, commit, comment): super(RepoCommitCommentEvent, self).__init__(repo) self.commit = commit self.comment = comment - name = 'repo-commit-comment' - display_name = lazy_ugettext('repository commit comment') + def as_dict(self): + data = super(RepoCommitCommentEvent, self).as_dict() + data['commit'] = { + 'commit_id': self.commit.raw_id, + 'commit_message': self.commit.message, + 'commit_branch': self.commit.branch, + } + + data['comment'] = { + 'comment_id': self.comment.comment_id, + 'comment_text': self.comment.text, + 'comment_type': self.comment.comment_type, + 'comment_f_path': self.comment.f_path, + 'comment_line_no': self.comment.line_no, + } + return data class RepoPreCreateEvent(RepoEvent): @@ -202,6 +222,7 @@ class RepoPreCreateEvent(RepoEvent): """ name = 'repo-pre-create' display_name = lazy_ugettext('repository pre create') + description = lazy_ugettext('Event triggered before repository is created') class RepoCreateEvent(RepoEvent): @@ -211,6 +232,7 @@ class RepoCreateEvent(RepoEvent): """ name = 'repo-create' display_name = lazy_ugettext('repository created') + description = lazy_ugettext('Event triggered after repository was created') class RepoPreDeleteEvent(RepoEvent): @@ -220,6 +242,7 @@ class RepoPreDeleteEvent(RepoEvent): """ name = 'repo-pre-delete' display_name = lazy_ugettext('repository pre delete') + description = lazy_ugettext('Event triggered before a repository is deleted') class RepoDeleteEvent(RepoEvent): @@ -229,6 +252,7 @@ class RepoDeleteEvent(RepoEvent): """ name = 'repo-delete' display_name = lazy_ugettext('repository deleted') + description = lazy_ugettext('Event triggered after repository was deleted') class RepoVCSEvent(RepoEvent): @@ -269,6 +293,7 @@ class RepoPrePullEvent(RepoVCSEvent): """ name = 'repo-pre-pull' display_name = lazy_ugettext('repository pre pull') + description = lazy_ugettext('Event triggered before repository code is pulled') class RepoPullEvent(RepoVCSEvent): @@ -278,6 +303,7 @@ class RepoPullEvent(RepoVCSEvent): """ name = 'repo-pull' display_name = lazy_ugettext('repository pull') + description = lazy_ugettext('Event triggered after repository code was pulled') class RepoPrePushEvent(RepoVCSEvent): @@ -287,6 +313,8 @@ class RepoPrePushEvent(RepoVCSEvent): """ name = 'repo-pre-push' display_name = lazy_ugettext('repository pre push') + description = lazy_ugettext('Event triggered before the code is ' + 'pushed to a repository') class RepoPushEvent(RepoVCSEvent): @@ -298,6 +326,8 @@ class RepoPushEvent(RepoVCSEvent): """ name = 'repo-push' display_name = lazy_ugettext('repository push') + description = lazy_ugettext('Event triggered after the code was ' + 'pushed to a repository') def __init__(self, repo_name, pushed_commit_ids, extras): super(RepoPushEvent, self).__init__(repo_name, extras) diff --git a/rhodecode/events/repo_group.py b/rhodecode/events/repo_group.py --- a/rhodecode/events/repo_group.py +++ b/rhodecode/events/repo_group.py @@ -60,6 +60,7 @@ class RepoGroupCreateEvent(RepoGroupEven """ name = 'repo-group-create' display_name = lazy_ugettext('repository group created') + description = lazy_ugettext('Event triggered after a repository group was created') class RepoGroupDeleteEvent(RepoGroupEvent): @@ -69,6 +70,7 @@ class RepoGroupDeleteEvent(RepoGroupEven """ name = 'repo-group-delete' display_name = lazy_ugettext('repository group deleted') + description = lazy_ugettext('Event triggered after a repository group was deleted') class RepoGroupUpdateEvent(RepoGroupEvent): @@ -78,3 +80,4 @@ class RepoGroupUpdateEvent(RepoGroupEven """ name = 'repo-group-update' display_name = lazy_ugettext('repository group update') + description = lazy_ugettext('Event triggered after a repository group was updated') diff --git a/rhodecode/integrations/types/base.py b/rhodecode/integrations/types/base.py --- a/rhodecode/integrations/types/base.py +++ b/rhodecode/integrations/types/base.py @@ -125,6 +125,19 @@ class IntegrationTypeBase(object): """ return colander.Schema() + def event_enabled(self, event): + """ + Checks if submitted event is enabled based on the plugin settings + :param event: + :return: bool + """ + allowed_events = self.settings['events'] + if event.name not in allowed_events: + log.debug('event ignored: %r event %s not in allowed set of events %s', + event, event.name, allowed_events) + return False + return True + class EEIntegration(IntegrationTypeBase): description = 'Integration available in RhodeCode EE edition.' @@ -139,30 +152,57 @@ class EEIntegration(IntegrationTypeBase) # Helpers # # updating this required to update the `common_vars` as well. WEBHOOK_URL_VARS = [ - ('event_name', 'Unique name of the event type, e.g pullrequest-update'), - ('repo_name', 'Full name of the repository'), - ('repo_type', 'VCS type of repository'), - ('repo_id', 'Unique id of repository'), - ('repo_url', 'Repository url'), + # GENERAL + ('General', [ + ('event_name', 'Unique name of the event type, e.g pullrequest-update'), + ('repo_name', 'Full name of the repository'), + ('repo_type', 'VCS type of repository'), + ('repo_id', 'Unique id of repository'), + ('repo_url', 'Repository url'), + ] + ), # extra repo fields - ('extra:', 'Extra repo variables, read from its settings.'), - + ('Repository', [ + ('extra:', 'Extra repo variables, read from its settings.'), + ] + ), # special attrs below that we handle, using multi-call - ('branch', 'Name of each branch submitted, if any.'), - ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'), - ('commit_id', 'ID (full sha) of each commit submitted, if any.'), - + ('Commit push - Multicalls', [ + ('branch', 'Name of each branch submitted, if any.'), + ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'), + ('commit_id', 'ID (full sha) of each commit submitted, if any.'), + ] + ), # pr events vars - ('pull_request_id', 'Unique ID of the pull request.'), - ('pull_request_title', 'Title of the pull request.'), - ('pull_request_url', 'Pull request url.'), - ('pull_request_shadow_url', 'Pull request shadow repo clone url.'), - ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. ' - 'Changes after PR update'), + ('Pull request', [ + ('pull_request_id', 'Unique ID of the pull request.'), + ('pull_request_title', 'Title of the pull request.'), + ('pull_request_url', 'Pull request url.'), + ('pull_request_shadow_url', 'Pull request shadow repo clone url.'), + ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. ' + 'Changes after PR update'), + ] + ), + # commit comment event vars + ('Commit comment', [ + ('commit_comment_id', 'Unique ID of the comment made on a commit.'), + ('commit_comment_text', 'Text of commit comment.'), + ('commit_comment_type', 'Type of comment, e.g note/todo.'), + ('commit_comment_f_path', 'Optionally path of file for inline comments.'), + ('commit_comment_line_no', 'Line number of the file: eg o10, or n200'), + + ('commit_comment_commit_id', 'Commit id that comment was left at.'), + ('commit_comment_commit_branch', 'Commit branch that comment was left at'), + ('commit_comment_commit_message', 'Commit message that comment was left at'), + ] + ), # user who triggers the call - ('username', 'User who triggered the call.'), - ('user_id', 'User id who triggered the call.'), + ('Caller', [ + ('username', 'User who triggered the call.'), + ('user_id', 'User id who triggered the call.'), + ] + ), ] # common vars for url template used for CI plugins. Shared with webhook @@ -271,6 +311,26 @@ class WebhookDataHandler(CommitParsingDa return url_calls + def repo_commit_comment_handler(self, event, data): + url = self.get_base_parsed_template(data) + log.debug('register %s call(%s) to url %s', self.name, event, url) + comment_vars = [ + ('commit_comment_id', data['comment']['comment_id']), + ('commit_comment_text', data['comment']['comment_text']), + ('commit_comment_type', data['comment']['comment_type']), + + ('commit_comment_f_path', data['comment']['comment_f_path']), + ('commit_comment_line_no', data['comment']['comment_line_no']), + + ('commit_comment_commit_id', data['commit']['commit_id']), + ('commit_comment_commit_branch', data['commit']['commit_branch']), + ('commit_comment_commit_message', data['commit']['commit_message']), + ] + for k, v in comment_vars: + url = UrlTmpl(url).safe_substitute(**{k: v}) + + return [(url, self.headers, data)] + def repo_create_event_handler(self, event, data): url = self.get_base_parsed_template(data) log.debug('register %s call(%s) to url %s', self.name, event, url) @@ -298,12 +358,13 @@ class WebhookDataHandler(CommitParsingDa return self.repo_push_event_handler(event, data) elif isinstance(event, events.RepoCreateEvent): return self.repo_create_event_handler(event, data) + elif isinstance(event, events.RepoCommitCommentEvent): + return self.repo_commit_comment_handler(event, data) elif isinstance(event, events.PullRequestEvent): return self.pull_request_event_handler(event, data) else: raise ValueError( - 'event type `%s` not in supported list: %s' % ( - event.__class__, events)) + 'event type `{}` has no handler defined'.format(event.__class__)) def get_auth(settings): @@ -320,9 +381,13 @@ def get_web_token(settings): def get_url_vars(url_vars): - return '\n'.join( - '{} - {}'.format('${' + key + '}', explanation) - for key, explanation in url_vars) + items = [] + + for section, section_items in url_vars: + items.append('\n*{}*'.format(section)) + for key, explanation in section_items: + items.append(' {} - {}'.format('${' + key + '}', explanation)) + return '\n'.join(items) def render_with_traceback(template, *args, **kwargs): diff --git a/rhodecode/integrations/types/email.py b/rhodecode/integrations/types/email.py --- a/rhodecode/integrations/types/email.py +++ b/rhodecode/integrations/types/email.py @@ -19,13 +19,14 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ from __future__ import unicode_literals -import deform import logging + import colander - +import deform.widget from mako.template import Template from rhodecode import events +from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc from rhodecode.translation import _ from rhodecode.lib.celerylib import run_task from rhodecode.lib.celerylib import tasks @@ -174,6 +175,10 @@ class EmailIntegrationType(IntegrationTy display_name = _('Email') description = _('Send repo push summaries to a list of recipients via email') + valid_events = [ + events.RepoPushEvent + ] + @classmethod def icon(cls): return ''' @@ -240,59 +245,86 @@ class EmailIntegrationType(IntegrationTy def settings_schema(self): schema = EmailSettingsSchema() + schema.add(colander.SchemaNode( + colander.Set(), + widget=CheckboxChoiceWidgetDesc( + values=sorted( + [(e.name, e.display_name, e.description) for e in self.valid_events] + ), + ), + description="List of events activated for this integration", + name='events' + )) return schema def send_event(self, event): - data = event.as_dict() - log.debug('got event: %r', event) + log.debug('handling event %s with integration %s', event.name, self) + + if event.__class__ not in self.valid_events: + log.debug('event %r not present in valid event list (%s)', event, self.valid_events) + return + + if not self.event_enabled(event): + # NOTE(marcink): for legacy reasons we're skipping this check... + # since the email event haven't had any settings... + pass + handler = EmailEventHandler(self.settings) + handler(event, event_data=event.as_dict()) + + +class EmailEventHandler(object): + def __init__(self, integration_settings): + self.integration_settings = integration_settings + + def __call__(self, event, event_data): if isinstance(event, events.RepoPushEvent): - repo_push_handler(data, self.settings) + self.repo_push_handler(event, event_data) else: log.debug('ignoring event: %r', event) - -def repo_push_handler(data, settings): - commit_num = len(data['push']['commits']) - server_url = data['server_url'] + def repo_push_handler(self, event, data): + commit_num = len(data['push']['commits']) + server_url = data['server_url'] - if commit_num == 1: - if data['push']['branches']: - _subject = '[{repo_name}] {author} pushed {commit_num} commit on branches: {branches}' - else: - _subject = '[{repo_name}] {author} pushed {commit_num} commit' - subject = _subject.format( - author=data['actor']['username'], - repo_name=data['repo']['repo_name'], - commit_num=commit_num, - branches=', '.join( - branch['name'] for branch in data['push']['branches']) - ) - else: - if data['push']['branches']: - _subject = '[{repo_name}] {author} pushed {commit_num} commits on branches: {branches}' + if commit_num == 1: + if data['push']['branches']: + _subject = '[{repo_name}] {author} pushed {commit_num} commit on branches: {branches}' + else: + _subject = '[{repo_name}] {author} pushed {commit_num} commit' + subject = _subject.format( + author=data['actor']['username'], + repo_name=data['repo']['repo_name'], + commit_num=commit_num, + branches=', '.join( + branch['name'] for branch in data['push']['branches']) + ) else: - _subject = '[{repo_name}] {author} pushed {commit_num} commits' - subject = _subject.format( - author=data['actor']['username'], - repo_name=data['repo']['repo_name'], - commit_num=commit_num, - branches=', '.join( - branch['name'] for branch in data['push']['branches'])) + if data['push']['branches']: + _subject = '[{repo_name}] {author} pushed {commit_num} commits on branches: {branches}' + else: + _subject = '[{repo_name}] {author} pushed {commit_num} commits' + subject = _subject.format( + author=data['actor']['username'], + repo_name=data['repo']['repo_name'], + commit_num=commit_num, + branches=', '.join( + branch['name'] for branch in data['push']['branches'])) - email_body_plaintext = render_with_traceback( - REPO_PUSH_TEMPLATE_PLAINTEXT, - data=data, - subject=subject, - instance_url=server_url) + email_body_plaintext = render_with_traceback( + REPO_PUSH_TEMPLATE_PLAINTEXT, + data=data, + subject=subject, + instance_url=server_url) - email_body_html = render_with_traceback( - REPO_PUSH_TEMPLATE_HTML, - data=data, - subject=subject, - instance_url=server_url) + email_body_html = render_with_traceback( + REPO_PUSH_TEMPLATE_HTML, + data=data, + subject=subject, + instance_url=server_url) - for email_address in settings['recipients']: - run_task( - tasks.send_email, email_address, subject, - email_body_plaintext, email_body_html) + recipients = self.integration_settings['recipients'] + for email_address in recipients: + run_task( + tasks.send_email, email_address, subject, + email_body_plaintext, email_body_html) diff --git a/rhodecode/integrations/types/hipchat.py b/rhodecode/integrations/types/hipchat.py --- a/rhodecode/integrations/types/hipchat.py +++ b/rhodecode/integrations/types/hipchat.py @@ -26,6 +26,7 @@ import colander import textwrap from mako.template import Template from rhodecode import events +from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc from rhodecode.translation import _ from rhodecode.lib import helpers as h from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask @@ -119,13 +120,10 @@ class HipchatIntegrationType(Integration def send_event(self, event): if event.__class__ not in self.valid_events: - log.debug('event not valid: %r', event) + log.debug('event %r not present in valid event list (%s)', event, self.valid_events) return - allowed_events = self.settings['events'] - if event.name not in allowed_events: - log.debug('event ignored: %r event %s not in allowed events %s', - event, event.name, allowed_events) + if not self.event_enabled(event): return data = event.as_dict() @@ -133,8 +131,6 @@ class HipchatIntegrationType(Integration text = '%s caused a %s event' % ( data['actor']['username'], event.name) - log.debug('handling hipchat event for %s', event.name) - if isinstance(event, events.PullRequestCommentEvent): text = self.format_pull_request_comment_event(event, data) elif isinstance(event, events.PullRequestReviewEvent): @@ -154,12 +150,12 @@ class HipchatIntegrationType(Integration schema = HipchatSettingsSchema() schema.add(colander.SchemaNode( colander.Set(), - widget=deform.widget.CheckboxChoiceWidget( + widget=CheckboxChoiceWidgetDesc( values=sorted( - [(e.name, e.display_name) for e in self.valid_events] - ) + [(e.name, e.display_name, e.description) for e in self.valid_events] + ), ), - description="Events activated for this integration", + description="List of events activated for this integration", name='events' )) diff --git a/rhodecode/integrations/types/slack.py b/rhodecode/integrations/types/slack.py --- a/rhodecode/integrations/types/slack.py +++ b/rhodecode/integrations/types/slack.py @@ -30,6 +30,7 @@ import colander from mako.template import Template from rhodecode import events +from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc from rhodecode.translation import _ from rhodecode.lib import helpers as h from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask @@ -134,14 +135,13 @@ class SlackIntegrationType(IntegrationTy ] def send_event(self, event): + log.debug('handling event %s with integration %s', event.name, self) + if event.__class__ not in self.valid_events: - log.debug('event not valid: %r', event) + log.debug('event %r not present in valid event list (%s)', event, self.valid_events) return - allowed_events = self.settings['events'] - if event.name not in allowed_events: - log.debug('event ignored: %r event %s not in allowed events %s', - event, event.name, allowed_events) + if not self.event_enabled(event): return data = event.as_dict() @@ -154,8 +154,6 @@ class SlackIntegrationType(IntegrationTy fields = None overrides = None - log.debug('handling slack event for %s', event.name) - if isinstance(event, events.PullRequestCommentEvent): (title, text, fields, overrides) \ = self.format_pull_request_comment_event(event, data) @@ -176,12 +174,12 @@ class SlackIntegrationType(IntegrationTy schema = SlackSettingsSchema() schema.add(colander.SchemaNode( colander.Set(), - widget=deform.widget.CheckboxChoiceWidget( + widget=CheckboxChoiceWidgetDesc( values=sorted( - [(e.name, e.display_name) for e in self.valid_events] - ) + [(e.name, e.display_name, e.description) for e in self.valid_events] + ), ), - description="Events activated for this integration", + description="List of events activated for this integration", name='events' )) diff --git a/rhodecode/integrations/types/webhook.py b/rhodecode/integrations/types/webhook.py --- a/rhodecode/integrations/types/webhook.py +++ b/rhodecode/integrations/types/webhook.py @@ -20,13 +20,14 @@ from __future__ import unicode_literals -import deform import deform.widget import logging import colander import rhodecode from rhodecode import events +from rhodecode.lib.colander_utils import strip_whitespace +from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc from rhodecode.translation import _ from rhodecode.integrations.types.base import ( IntegrationTypeBase, get_auth, get_web_token, get_url_vars, @@ -53,11 +54,12 @@ class WebhookSettingsSchema(colander.Sch 'objects in data in such cases.'), missing=colander.required, required=True, + preparer=strip_whitespace, validator=colander.url, widget=widgets.CodeMirrorWidget( help_block_collapsable_name='Show url variables', help_block_collapsable=( - 'E.g http://my-serv/trigger_job/${{event_name}}' + 'E.g http://my-serv.com/trigger_job/${{event_name}}' '?PR_ID=${{pull_request_id}}' '\nFull list of vars:\n{}'.format(URL_VARS)), codemirror_mode='text', @@ -146,34 +148,31 @@ class WebhookIntegrationType(Integration events.PullRequestCreateEvent, events.RepoPushEvent, events.RepoCreateEvent, + events.RepoCommitCommentEvent, ] def settings_schema(self): schema = WebhookSettingsSchema() schema.add(colander.SchemaNode( colander.Set(), - widget=deform.widget.CheckboxChoiceWidget( + widget=CheckboxChoiceWidgetDesc( values=sorted( - [(e.name, e.display_name) for e in self.valid_events] - ) + [(e.name, e.display_name, e.description) for e in self.valid_events] + ), ), - description="Events activated for this integration", + description="List of events activated for this integration", name='events' )) return schema def send_event(self, event): - log.debug( - 'handling event %s with Webhook integration %s', event.name, self) + log.debug('handling event %s with integration %s', event.name, self) if event.__class__ not in self.valid_events: - log.debug('event not valid: %r', event) + log.debug('event %r not present in valid event list (%s)', event, self.valid_events) return - allowed_events = self.settings['events'] - if event.name not in allowed_events: - log.debug('event ignored: %r event %s not in allowed events %s', - event, event.name, allowed_events) + if not self.event_enabled(event): return data = event.as_dict() diff --git a/rhodecode/model/validation_schema/widgets.py b/rhodecode/model/validation_schema/widgets.py --- a/rhodecode/model/validation_schema/widgets.py +++ b/rhodecode/model/validation_schema/widgets.py @@ -20,13 +20,40 @@ import logging -import deform import deform.widget +from deform.widget import null, OptGroup, string_types + +log = logging.getLogger(__name__) -log = logging.getLogger(__name__) +def _normalize_choices(values): + result = [] + for item in values: + if isinstance(item, OptGroup): + normalized_options = _normalize_choices(item.options) + result.append(OptGroup(item.label, *normalized_options)) + else: + value, description, help_block = item + if not isinstance(value, string_types): + value = str(value) + result.append((value, description, help_block)) + return result class CodeMirrorWidget(deform.widget.TextAreaWidget): template = 'codemirror' requirements = (('deform', None), ('codemirror', None)) + + +class CheckboxChoiceWidgetDesc(deform.widget.CheckboxChoiceWidget): + template = "checkbox_choice_desc" + + def serialize(self, field, cstruct, **kw): + if cstruct in (null, None): + cstruct = () + readonly = kw.get("readonly", self.readonly) + values = kw.get("values", self.values) + kw["values"] = _normalize_choices(values) + template = readonly and self.readonly_template or self.template + tmpl_values = self.get_template_values(field, cstruct, kw) + return field.renderer(template, **tmpl_values) diff --git a/rhodecode/templates/forms/checkbox_choice.pt b/rhodecode/templates/forms/checkbox_choice.pt --- a/rhodecode/templates/forms/checkbox_choice.pt +++ b/rhodecode/templates/forms/checkbox_choice.pt @@ -22,4 +22,4 @@ ${field.end_sequence()} - \ No newline at end of file + diff --git a/rhodecode/templates/forms/checkbox_choice_desc.pt b/rhodecode/templates/forms/checkbox_choice_desc.pt new file mode 100644 --- /dev/null +++ b/rhodecode/templates/forms/checkbox_choice_desc.pt @@ -0,0 +1,26 @@ +
+ ${field.start_sequence()} +
+
+ + +

${help_block}

+
+
+ ${field.end_sequence()} +
diff --git a/rhodecode/tests/events/test_pullrequest.py b/rhodecode/tests/events/test_pullrequest.py --- a/rhodecode/tests/events/test_pullrequest.py +++ b/rhodecode/tests/events/test_pullrequest.py @@ -40,7 +40,7 @@ from rhodecode.events import ( PullRequestUpdateEvent, PullRequestReviewEvent, PullRequestMergeEvent, - PullRequestCloseEvent, + PullRequestCloseEvent ]) def test_pullrequest_events_serialized(EventClass, pr_util, config_stub): pr = pr_util.create_pull_request() diff --git a/rhodecode/tests/events/test_repo.py b/rhodecode/tests/events/test_repo.py --- a/rhodecode/tests/events/test_repo.py +++ b/rhodecode/tests/events/test_repo.py @@ -20,6 +20,7 @@ import pytest +from rhodecode.lib.utils2 import StrictAttributeDict from rhodecode.tests.events.conftest import EventCatcher from rhodecode.lib import hooks_base, utils2 @@ -28,7 +29,7 @@ from rhodecode.events.repo import ( RepoPrePullEvent, RepoPullEvent, RepoPrePushEvent, RepoPushEvent, RepoPreCreateEvent, RepoCreateEvent, - RepoPreDeleteEvent, RepoDeleteEvent, + RepoPreDeleteEvent, RepoDeleteEvent, RepoCommitCommentEvent, ) @@ -121,3 +122,24 @@ def test_push_fires_events(scm_extras): hooks_base.post_pull(scm_extras) assert event_catcher.events_types == [RepoPullEvent] + +@pytest.mark.parametrize('EventClass', [RepoCommitCommentEvent]) +def test_repo_commit_event(config_stub, repo_stub, EventClass): + + commit = StrictAttributeDict({ + 'raw_id': 'raw_id', + 'message': 'message', + 'branch': 'branch', + }) + + comment = StrictAttributeDict({ + 'comment_id': 'comment_id', + 'text': 'text', + 'comment_type': 'comment_type', + 'f_path': 'f_path', + 'line_no': 'line_no', + }) + event = EventClass(repo=repo_stub, commit=commit, comment=comment) + data = event.as_dict() + assert data['commit']['commit_id'] + assert data['comment']['comment_id'] diff --git a/rhodecode/tests/integrations/test_integration.py b/rhodecode/tests/integrations/test_integration.py --- a/rhodecode/tests/integrations/test_integration.py +++ b/rhodecode/tests/integrations/test_integration.py @@ -49,6 +49,7 @@ class TestDeleteScopesDeletesIntegration count = 1 + def counter(): global count val = count diff --git a/rhodecode/tests/integrations/test_webhook.py b/rhodecode/tests/integrations/test_webhook.py --- a/rhodecode/tests/integrations/test_webhook.py +++ b/rhodecode/tests/integrations/test_webhook.py @@ -52,8 +52,7 @@ def test_webhook_parse_url_invalid_event handler(event, {}) err = str(err.value) - assert err.startswith( - 'event type `%s` not in supported list' % event.__class__) + assert err == "event type `` has no handler defined" @pytest.mark.parametrize('template,expected_urls', [