events: added support for pull-request-comment and commit-comment events.

- allows chopts like actions on commits
- re organized events and code cleanups
This commit is contained in:
Marcin Kuzminski 2020-04-17 22:08:42 +02:00
parent 875bda9f88
commit 324544678c
17 changed files with 325 additions and 113 deletions

View file

@ -75,4 +75,5 @@ from rhodecode.events.pullrequest import ( # pragma: no cover
PullRequestReviewEvent,
PullRequestMergeEvent,
PullRequestCloseEvent,
PullRequestCommentEvent,
)

View file

@ -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(RhodecodeEvent):
"""
Special subclass for Integration events
"""
description = ''

View file

@ -77,6 +77,7 @@ class PullRequestCreateEvent(PullRequestEvent):
"""
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(PullRequestEvent):
"""
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(PullRequestEvent):
"""
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(PullRequestEvent):
"""
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(PullRequestEvent):
"""
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(PullRequestEvent):
"""
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)

View file

@ -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)

View file

@ -60,6 +60,7 @@ class RepoGroupCreateEvent(RepoGroupEvent):
"""
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(RepoGroupEvent):
"""
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(RepoGroupEvent):
"""
name = 'repo-group-update'
display_name = lazy_ugettext('repository group update')
description = lazy_ugettext('Event triggered after a repository group was updated')

View file

@ -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_key_name>', 'Extra repo variables, read from its settings.'),
('Repository', [
('extra:<extra_key_name>', '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(CommitParsingDataHandler):
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(CommitParsingDataHandler):
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):

View file

@ -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 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(IntegrationTypeBase):
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(IntegrationTypeBase):
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(self, event, data):
commit_num = len(data['push']['commits'])
server_url = data['server_url']
def repo_push_handler(data, settings):
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}'
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} 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}'
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)

View file

@ -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(IntegrationTypeBase, CommitParsingDataHandler):
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(IntegrationTypeBase, CommitParsingDataHandler):
text = '<b>%s<b> caused a <b>%s</b> 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(IntegrationTypeBase, CommitParsingDataHandler):
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'
))

View file

@ -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(IntegrationTypeBase, CommitParsingDataHandler):
]
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(IntegrationTypeBase, CommitParsingDataHandler):
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(IntegrationTypeBase, CommitParsingDataHandler):
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'
))

View file

@ -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.Schema):
'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(IntegrationTypeBase):
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()

View file

@ -20,13 +20,40 @@
import logging
import deform
import deform.widget
from deform.widget import null, OptGroup, string_types
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)

View file

@ -22,4 +22,4 @@
</div>
</div>
${field.end_sequence()}
</div>
</div>

View file

@ -0,0 +1,26 @@
<div tal:define="css_class css_class|field.widget.css_class;
style style|field.widget.style;
oid oid|field.oid;
inline getattr(field.widget, 'inline', False)"
tal:omit-tag="not inline">
${field.start_sequence()}
<div tal:repeat="choice values | field.widget.values"
tal:omit-tag="inline"
class="checkbox">
<div tal:define="(value, title, help_block) choice">
<input tal:attributes="checked value in cstruct;
class css_class;
style style"
type="checkbox"
name="checkbox"
value="${value}"
id="${oid}-${repeat.choice.index}"/>
<label for="${oid}-${repeat.choice.index}"
tal:attributes="class inline and 'checkbox-inline'">
${title}
</label>
<p tal:condition="help_block" class="help-block">${help_block}</p>
</div>
</div>
${field.end_sequence()}
</div>

View file

@ -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()

View file

@ -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']

View file

@ -49,6 +49,7 @@ class TestDeleteScopesDeletesIntegrations(object):
count = 1
def counter():
global count
val = count

View file

@ -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 `<class 'rhodecode.events.repo.RepoDeleteEvent'>` has no handler defined"
@pytest.mark.parametrize('template,expected_urls', [