Show More
@@ -0,0 +1,26 b'' | |||
|
1 | <div tal:define="css_class css_class|field.widget.css_class; | |
|
2 | style style|field.widget.style; | |
|
3 | oid oid|field.oid; | |
|
4 | inline getattr(field.widget, 'inline', False)" | |
|
5 | tal:omit-tag="not inline"> | |
|
6 | ${field.start_sequence()} | |
|
7 | <div tal:repeat="choice values | field.widget.values" | |
|
8 | tal:omit-tag="inline" | |
|
9 | class="checkbox"> | |
|
10 | <div tal:define="(value, title, help_block) choice"> | |
|
11 | <input tal:attributes="checked value in cstruct; | |
|
12 | class css_class; | |
|
13 | style style" | |
|
14 | type="checkbox" | |
|
15 | name="checkbox" | |
|
16 | value="${value}" | |
|
17 | id="${oid}-${repeat.choice.index}"/> | |
|
18 | <label for="${oid}-${repeat.choice.index}" | |
|
19 | tal:attributes="class inline and 'checkbox-inline'"> | |
|
20 | ${title} | |
|
21 | </label> | |
|
22 | <p tal:condition="help_block" class="help-block">${help_block}</p> | |
|
23 | </div> | |
|
24 | </div> | |
|
25 | ${field.end_sequence()} | |
|
26 | </div> |
@@ -75,4 +75,5 b' from rhodecode.events.pullrequest import' | |||
|
75 | 75 | PullRequestReviewEvent, |
|
76 | 76 | PullRequestMergeEvent, |
|
77 | 77 | PullRequestCloseEvent, |
|
78 | PullRequestCommentEvent, | |
|
78 | 79 | ) |
@@ -44,6 +44,9 b' class RhodecodeEvent(object):' | |||
|
44 | 44 | self._request = request |
|
45 | 45 | self.utc_timestamp = datetime.datetime.utcnow() |
|
46 | 46 | |
|
47 | def __repr__(self): | |
|
48 | return '<%s:(%s)>' % (self.__class__.__name__, self.name) | |
|
49 | ||
|
47 | 50 | def get_request(self): |
|
48 | 51 | if self._request: |
|
49 | 52 | return self._request |
@@ -116,3 +119,4 b' class RhodeCodeIntegrationEvent(Rhodecod' | |||
|
116 | 119 | """ |
|
117 | 120 | Special subclass for Integration events |
|
118 | 121 | """ |
|
122 | description = '' |
@@ -77,6 +77,7 b' class PullRequestCreateEvent(PullRequest' | |||
|
77 | 77 | """ |
|
78 | 78 | name = 'pullrequest-create' |
|
79 | 79 | display_name = lazy_ugettext('pullrequest created') |
|
80 | description = lazy_ugettext('Event triggered after pull request was created') | |
|
80 | 81 | |
|
81 | 82 | |
|
82 | 83 | class PullRequestCloseEvent(PullRequestEvent): |
@@ -86,6 +87,7 b' class PullRequestCloseEvent(PullRequestE' | |||
|
86 | 87 | """ |
|
87 | 88 | name = 'pullrequest-close' |
|
88 | 89 | display_name = lazy_ugettext('pullrequest closed') |
|
90 | description = lazy_ugettext('Event triggered after pull request was closed') | |
|
89 | 91 | |
|
90 | 92 | |
|
91 | 93 | class PullRequestUpdateEvent(PullRequestEvent): |
@@ -95,6 +97,7 b' class PullRequestUpdateEvent(PullRequest' | |||
|
95 | 97 | """ |
|
96 | 98 | name = 'pullrequest-update' |
|
97 | 99 | display_name = lazy_ugettext('pullrequest commits updated') |
|
100 | description = lazy_ugettext('Event triggered after pull requests was updated') | |
|
98 | 101 | |
|
99 | 102 | |
|
100 | 103 | class PullRequestReviewEvent(PullRequestEvent): |
@@ -104,6 +107,8 b' class PullRequestReviewEvent(PullRequest' | |||
|
104 | 107 | """ |
|
105 | 108 | name = 'pullrequest-review' |
|
106 | 109 | display_name = lazy_ugettext('pullrequest review changed') |
|
110 | description = lazy_ugettext('Event triggered after a review status of a ' | |
|
111 | 'pull requests has changed to other.') | |
|
107 | 112 | |
|
108 | 113 | def __init__(self, pullrequest, status): |
|
109 | 114 | super(PullRequestReviewEvent, self).__init__(pullrequest) |
@@ -117,6 +122,8 b' class PullRequestMergeEvent(PullRequestE' | |||
|
117 | 122 | """ |
|
118 | 123 | name = 'pullrequest-merge' |
|
119 | 124 | display_name = lazy_ugettext('pullrequest merged') |
|
125 | description = lazy_ugettext('Event triggered after a successful merge operation ' | |
|
126 | 'was executed on a pull request') | |
|
120 | 127 | |
|
121 | 128 | |
|
122 | 129 | class PullRequestCommentEvent(PullRequestEvent): |
@@ -126,6 +133,8 b' class PullRequestCommentEvent(PullReques' | |||
|
126 | 133 | """ |
|
127 | 134 | name = 'pullrequest-comment' |
|
128 | 135 | display_name = lazy_ugettext('pullrequest commented') |
|
136 | description = lazy_ugettext('Event triggered after a comment was made on a code ' | |
|
137 | 'in the pull request') | |
|
129 | 138 | |
|
130 | 139 | def __init__(self, pullrequest, comment): |
|
131 | 140 | super(PullRequestCommentEvent, self).__init__(pullrequest) |
@@ -186,13 +186,33 b' class RepoCommitCommentEvent(RepoEvent):' | |||
|
186 | 186 | An instance of this class is emitted as an :term:`event` after a comment is made |
|
187 | 187 | on repository commit. |
|
188 | 188 | """ |
|
189 | ||
|
190 | name = 'repo-commit-comment' | |
|
191 | display_name = lazy_ugettext('repository commit comment') | |
|
192 | description = lazy_ugettext('Event triggered after a comment was made ' | |
|
193 | 'on commit inside a repository') | |
|
194 | ||
|
189 | 195 | def __init__(self, repo, commit, comment): |
|
190 | 196 | super(RepoCommitCommentEvent, self).__init__(repo) |
|
191 | 197 | self.commit = commit |
|
192 | 198 | self.comment = comment |
|
193 | 199 | |
|
194 | name = 'repo-commit-comment' | |
|
195 | display_name = lazy_ugettext('repository commit comment') | |
|
200 | def as_dict(self): | |
|
201 | data = super(RepoCommitCommentEvent, self).as_dict() | |
|
202 | data['commit'] = { | |
|
203 | 'commit_id': self.commit.raw_id, | |
|
204 | 'commit_message': self.commit.message, | |
|
205 | 'commit_branch': self.commit.branch, | |
|
206 | } | |
|
207 | ||
|
208 | data['comment'] = { | |
|
209 | 'comment_id': self.comment.comment_id, | |
|
210 | 'comment_text': self.comment.text, | |
|
211 | 'comment_type': self.comment.comment_type, | |
|
212 | 'comment_f_path': self.comment.f_path, | |
|
213 | 'comment_line_no': self.comment.line_no, | |
|
214 | } | |
|
215 | return data | |
|
196 | 216 | |
|
197 | 217 | |
|
198 | 218 | class RepoPreCreateEvent(RepoEvent): |
@@ -202,6 +222,7 b' class RepoPreCreateEvent(RepoEvent):' | |||
|
202 | 222 | """ |
|
203 | 223 | name = 'repo-pre-create' |
|
204 | 224 | display_name = lazy_ugettext('repository pre create') |
|
225 | description = lazy_ugettext('Event triggered before repository is created') | |
|
205 | 226 | |
|
206 | 227 | |
|
207 | 228 | class RepoCreateEvent(RepoEvent): |
@@ -211,6 +232,7 b' class RepoCreateEvent(RepoEvent):' | |||
|
211 | 232 | """ |
|
212 | 233 | name = 'repo-create' |
|
213 | 234 | display_name = lazy_ugettext('repository created') |
|
235 | description = lazy_ugettext('Event triggered after repository was created') | |
|
214 | 236 | |
|
215 | 237 | |
|
216 | 238 | class RepoPreDeleteEvent(RepoEvent): |
@@ -220,6 +242,7 b' class RepoPreDeleteEvent(RepoEvent):' | |||
|
220 | 242 | """ |
|
221 | 243 | name = 'repo-pre-delete' |
|
222 | 244 | display_name = lazy_ugettext('repository pre delete') |
|
245 | description = lazy_ugettext('Event triggered before a repository is deleted') | |
|
223 | 246 | |
|
224 | 247 | |
|
225 | 248 | class RepoDeleteEvent(RepoEvent): |
@@ -229,6 +252,7 b' class RepoDeleteEvent(RepoEvent):' | |||
|
229 | 252 | """ |
|
230 | 253 | name = 'repo-delete' |
|
231 | 254 | display_name = lazy_ugettext('repository deleted') |
|
255 | description = lazy_ugettext('Event triggered after repository was deleted') | |
|
232 | 256 | |
|
233 | 257 | |
|
234 | 258 | class RepoVCSEvent(RepoEvent): |
@@ -269,6 +293,7 b' class RepoPrePullEvent(RepoVCSEvent):' | |||
|
269 | 293 | """ |
|
270 | 294 | name = 'repo-pre-pull' |
|
271 | 295 | display_name = lazy_ugettext('repository pre pull') |
|
296 | description = lazy_ugettext('Event triggered before repository code is pulled') | |
|
272 | 297 | |
|
273 | 298 | |
|
274 | 299 | class RepoPullEvent(RepoVCSEvent): |
@@ -278,6 +303,7 b' class RepoPullEvent(RepoVCSEvent):' | |||
|
278 | 303 | """ |
|
279 | 304 | name = 'repo-pull' |
|
280 | 305 | display_name = lazy_ugettext('repository pull') |
|
306 | description = lazy_ugettext('Event triggered after repository code was pulled') | |
|
281 | 307 | |
|
282 | 308 | |
|
283 | 309 | class RepoPrePushEvent(RepoVCSEvent): |
@@ -287,6 +313,8 b' class RepoPrePushEvent(RepoVCSEvent):' | |||
|
287 | 313 | """ |
|
288 | 314 | name = 'repo-pre-push' |
|
289 | 315 | display_name = lazy_ugettext('repository pre push') |
|
316 | description = lazy_ugettext('Event triggered before the code is ' | |
|
317 | 'pushed to a repository') | |
|
290 | 318 | |
|
291 | 319 | |
|
292 | 320 | class RepoPushEvent(RepoVCSEvent): |
@@ -298,6 +326,8 b' class RepoPushEvent(RepoVCSEvent):' | |||
|
298 | 326 | """ |
|
299 | 327 | name = 'repo-push' |
|
300 | 328 | display_name = lazy_ugettext('repository push') |
|
329 | description = lazy_ugettext('Event triggered after the code was ' | |
|
330 | 'pushed to a repository') | |
|
301 | 331 | |
|
302 | 332 | def __init__(self, repo_name, pushed_commit_ids, extras): |
|
303 | 333 | super(RepoPushEvent, self).__init__(repo_name, extras) |
@@ -60,6 +60,7 b' class RepoGroupCreateEvent(RepoGroupEven' | |||
|
60 | 60 | """ |
|
61 | 61 | name = 'repo-group-create' |
|
62 | 62 | display_name = lazy_ugettext('repository group created') |
|
63 | description = lazy_ugettext('Event triggered after a repository group was created') | |
|
63 | 64 | |
|
64 | 65 | |
|
65 | 66 | class RepoGroupDeleteEvent(RepoGroupEvent): |
@@ -69,6 +70,7 b' class RepoGroupDeleteEvent(RepoGroupEven' | |||
|
69 | 70 | """ |
|
70 | 71 | name = 'repo-group-delete' |
|
71 | 72 | display_name = lazy_ugettext('repository group deleted') |
|
73 | description = lazy_ugettext('Event triggered after a repository group was deleted') | |
|
72 | 74 | |
|
73 | 75 | |
|
74 | 76 | class RepoGroupUpdateEvent(RepoGroupEvent): |
@@ -78,3 +80,4 b' class RepoGroupUpdateEvent(RepoGroupEven' | |||
|
78 | 80 | """ |
|
79 | 81 | name = 'repo-group-update' |
|
80 | 82 | display_name = lazy_ugettext('repository group update') |
|
83 | description = lazy_ugettext('Event triggered after a repository group was updated') |
@@ -125,6 +125,19 b' class IntegrationTypeBase(object):' | |||
|
125 | 125 | """ |
|
126 | 126 | return colander.Schema() |
|
127 | 127 | |
|
128 | def event_enabled(self, event): | |
|
129 | """ | |
|
130 | Checks if submitted event is enabled based on the plugin settings | |
|
131 | :param event: | |
|
132 | :return: bool | |
|
133 | """ | |
|
134 | allowed_events = self.settings['events'] | |
|
135 | if event.name not in allowed_events: | |
|
136 | log.debug('event ignored: %r event %s not in allowed set of events %s', | |
|
137 | event, event.name, allowed_events) | |
|
138 | return False | |
|
139 | return True | |
|
140 | ||
|
128 | 141 | |
|
129 | 142 | class EEIntegration(IntegrationTypeBase): |
|
130 | 143 | description = 'Integration available in RhodeCode EE edition.' |
@@ -139,30 +152,57 b' class EEIntegration(IntegrationTypeBase)' | |||
|
139 | 152 | # Helpers # |
|
140 | 153 | # updating this required to update the `common_vars` as well. |
|
141 | 154 | WEBHOOK_URL_VARS = [ |
|
142 | ('event_name', 'Unique name of the event type, e.g pullrequest-update'), | |
|
143 | ('repo_name', 'Full name of the repository'), | |
|
144 | ('repo_type', 'VCS type of repository'), | |
|
145 |
('repo_ |
|
|
146 |
('repo_ |
|
|
155 | # GENERAL | |
|
156 | ('General', [ | |
|
157 | ('event_name', 'Unique name of the event type, e.g pullrequest-update'), | |
|
158 | ('repo_name', 'Full name of the repository'), | |
|
159 | ('repo_type', 'VCS type of repository'), | |
|
160 | ('repo_id', 'Unique id of repository'), | |
|
161 | ('repo_url', 'Repository url'), | |
|
162 | ] | |
|
163 | ), | |
|
147 | 164 | # extra repo fields |
|
148 | ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'), | |
|
149 | ||
|
165 | ('Repository', [ | |
|
166 | ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'), | |
|
167 | ] | |
|
168 | ), | |
|
150 | 169 | # special attrs below that we handle, using multi-call |
|
151 | ('branch', 'Name of each branch submitted, if any.'), | |
|
152 |
('branch |
|
|
153 |
(' |
|
|
154 | ||
|
170 | ('Commit push - Multicalls', [ | |
|
171 | ('branch', 'Name of each branch submitted, if any.'), | |
|
172 | ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'), | |
|
173 | ('commit_id', 'ID (full sha) of each commit submitted, if any.'), | |
|
174 | ] | |
|
175 | ), | |
|
155 | 176 | # pr events vars |
|
156 | ('pull_request_id', 'Unique ID of the pull request.'), | |
|
157 |
('pull_request_ |
|
|
158 |
('pull_request_ |
|
|
159 |
('pull_request_ |
|
|
160 | ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. ' | |
|
161 | 'Changes after PR update'), | |
|
177 | ('Pull request', [ | |
|
178 | ('pull_request_id', 'Unique ID of the pull request.'), | |
|
179 | ('pull_request_title', 'Title of the pull request.'), | |
|
180 | ('pull_request_url', 'Pull request url.'), | |
|
181 | ('pull_request_shadow_url', 'Pull request shadow repo clone url.'), | |
|
182 | ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. ' | |
|
183 | 'Changes after PR update'), | |
|
184 | ] | |
|
185 | ), | |
|
186 | # commit comment event vars | |
|
187 | ('Commit comment', [ | |
|
188 | ('commit_comment_id', 'Unique ID of the comment made on a commit.'), | |
|
189 | ('commit_comment_text', 'Text of commit comment.'), | |
|
190 | ('commit_comment_type', 'Type of comment, e.g note/todo.'), | |
|
162 | 191 | |
|
192 | ('commit_comment_f_path', 'Optionally path of file for inline comments.'), | |
|
193 | ('commit_comment_line_no', 'Line number of the file: eg o10, or n200'), | |
|
194 | ||
|
195 | ('commit_comment_commit_id', 'Commit id that comment was left at.'), | |
|
196 | ('commit_comment_commit_branch', 'Commit branch that comment was left at'), | |
|
197 | ('commit_comment_commit_message', 'Commit message that comment was left at'), | |
|
198 | ] | |
|
199 | ), | |
|
163 | 200 | # user who triggers the call |
|
164 | ('username', 'User who triggered the call.'), | |
|
165 |
('user |
|
|
201 | ('Caller', [ | |
|
202 | ('username', 'User who triggered the call.'), | |
|
203 | ('user_id', 'User id who triggered the call.'), | |
|
204 | ] | |
|
205 | ), | |
|
166 | 206 | ] |
|
167 | 207 | |
|
168 | 208 | # common vars for url template used for CI plugins. Shared with webhook |
@@ -271,6 +311,26 b' class WebhookDataHandler(CommitParsingDa' | |||
|
271 | 311 | |
|
272 | 312 | return url_calls |
|
273 | 313 | |
|
314 | def repo_commit_comment_handler(self, event, data): | |
|
315 | url = self.get_base_parsed_template(data) | |
|
316 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
317 | comment_vars = [ | |
|
318 | ('commit_comment_id', data['comment']['comment_id']), | |
|
319 | ('commit_comment_text', data['comment']['comment_text']), | |
|
320 | ('commit_comment_type', data['comment']['comment_type']), | |
|
321 | ||
|
322 | ('commit_comment_f_path', data['comment']['comment_f_path']), | |
|
323 | ('commit_comment_line_no', data['comment']['comment_line_no']), | |
|
324 | ||
|
325 | ('commit_comment_commit_id', data['commit']['commit_id']), | |
|
326 | ('commit_comment_commit_branch', data['commit']['commit_branch']), | |
|
327 | ('commit_comment_commit_message', data['commit']['commit_message']), | |
|
328 | ] | |
|
329 | for k, v in comment_vars: | |
|
330 | url = UrlTmpl(url).safe_substitute(**{k: v}) | |
|
331 | ||
|
332 | return [(url, self.headers, data)] | |
|
333 | ||
|
274 | 334 | def repo_create_event_handler(self, event, data): |
|
275 | 335 | url = self.get_base_parsed_template(data) |
|
276 | 336 | log.debug('register %s call(%s) to url %s', self.name, event, url) |
@@ -298,12 +358,13 b' class WebhookDataHandler(CommitParsingDa' | |||
|
298 | 358 | return self.repo_push_event_handler(event, data) |
|
299 | 359 | elif isinstance(event, events.RepoCreateEvent): |
|
300 | 360 | return self.repo_create_event_handler(event, data) |
|
361 | elif isinstance(event, events.RepoCommitCommentEvent): | |
|
362 | return self.repo_commit_comment_handler(event, data) | |
|
301 | 363 | elif isinstance(event, events.PullRequestEvent): |
|
302 | 364 | return self.pull_request_event_handler(event, data) |
|
303 | 365 | else: |
|
304 | 366 | raise ValueError( |
|
305 | 'event type `%s` not in supported list: %s' % ( | |
|
306 | event.__class__, events)) | |
|
367 | 'event type `{}` has no handler defined'.format(event.__class__)) | |
|
307 | 368 | |
|
308 | 369 | |
|
309 | 370 | def get_auth(settings): |
@@ -320,9 +381,13 b' def get_web_token(settings):' | |||
|
320 | 381 | |
|
321 | 382 | |
|
322 | 383 | def get_url_vars(url_vars): |
|
323 | return '\n'.join( | |
|
324 | '{} - {}'.format('${' + key + '}', explanation) | |
|
325 | for key, explanation in url_vars) | |
|
384 | items = [] | |
|
385 | ||
|
386 | for section, section_items in url_vars: | |
|
387 | items.append('\n*{}*'.format(section)) | |
|
388 | for key, explanation in section_items: | |
|
389 | items.append(' {} - {}'.format('${' + key + '}', explanation)) | |
|
390 | return '\n'.join(items) | |
|
326 | 391 | |
|
327 | 392 | |
|
328 | 393 | def render_with_traceback(template, *args, **kwargs): |
@@ -19,13 +19,14 b'' | |||
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | from __future__ import unicode_literals |
|
22 | import deform | |
|
23 | 22 | import logging |
|
23 | ||
|
24 | 24 | import colander |
|
25 | ||
|
25 | import deform.widget | |
|
26 | 26 | from mako.template import Template |
|
27 | 27 | |
|
28 | 28 | from rhodecode import events |
|
29 | from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc | |
|
29 | 30 | from rhodecode.translation import _ |
|
30 | 31 | from rhodecode.lib.celerylib import run_task |
|
31 | 32 | from rhodecode.lib.celerylib import tasks |
@@ -174,6 +175,10 b' class EmailIntegrationType(IntegrationTy' | |||
|
174 | 175 | display_name = _('Email') |
|
175 | 176 | description = _('Send repo push summaries to a list of recipients via email') |
|
176 | 177 | |
|
178 | valid_events = [ | |
|
179 | events.RepoPushEvent | |
|
180 | ] | |
|
181 | ||
|
177 | 182 | @classmethod |
|
178 | 183 | def icon(cls): |
|
179 | 184 | return ''' |
@@ -240,59 +245,86 b' class EmailIntegrationType(IntegrationTy' | |||
|
240 | 245 | |
|
241 | 246 | def settings_schema(self): |
|
242 | 247 | schema = EmailSettingsSchema() |
|
248 | schema.add(colander.SchemaNode( | |
|
249 | colander.Set(), | |
|
250 | widget=CheckboxChoiceWidgetDesc( | |
|
251 | values=sorted( | |
|
252 | [(e.name, e.display_name, e.description) for e in self.valid_events] | |
|
253 | ), | |
|
254 | ), | |
|
255 | description="List of events activated for this integration", | |
|
256 | name='events' | |
|
257 | )) | |
|
243 | 258 | return schema |
|
244 | 259 | |
|
245 | 260 | def send_event(self, event): |
|
246 | data = event.as_dict() | |
|
247 | log.debug('got event: %r', event) | |
|
261 | log.debug('handling event %s with integration %s', event.name, self) | |
|
262 | ||
|
263 | if event.__class__ not in self.valid_events: | |
|
264 | log.debug('event %r not present in valid event list (%s)', event, self.valid_events) | |
|
265 | return | |
|
266 | ||
|
267 | if not self.event_enabled(event): | |
|
268 | # NOTE(marcink): for legacy reasons we're skipping this check... | |
|
269 | # since the email event haven't had any settings... | |
|
270 | pass | |
|
248 | 271 | |
|
272 | handler = EmailEventHandler(self.settings) | |
|
273 | handler(event, event_data=event.as_dict()) | |
|
274 | ||
|
275 | ||
|
276 | class EmailEventHandler(object): | |
|
277 | def __init__(self, integration_settings): | |
|
278 | self.integration_settings = integration_settings | |
|
279 | ||
|
280 | def __call__(self, event, event_data): | |
|
249 | 281 | if isinstance(event, events.RepoPushEvent): |
|
250 |
repo_push_handler( |
|
|
282 | self.repo_push_handler(event, event_data) | |
|
251 | 283 | else: |
|
252 | 284 | log.debug('ignoring event: %r', event) |
|
253 | 285 | |
|
254 | ||
|
255 | def repo_push_handler(data, settings): | |
|
256 | commit_num = len(data['push']['commits']) | |
|
257 | server_url = data['server_url'] | |
|
286 | def repo_push_handler(self, event, data): | |
|
287 | commit_num = len(data['push']['commits']) | |
|
288 | server_url = data['server_url'] | |
|
258 | 289 | |
|
259 | if commit_num == 1: | |
|
260 | if data['push']['branches']: | |
|
261 | _subject = '[{repo_name}] {author} pushed {commit_num} commit on branches: {branches}' | |
|
262 | else: | |
|
263 | _subject = '[{repo_name}] {author} pushed {commit_num} commit' | |
|
264 | subject = _subject.format( | |
|
265 | author=data['actor']['username'], | |
|
266 | repo_name=data['repo']['repo_name'], | |
|
267 | commit_num=commit_num, | |
|
268 | branches=', '.join( | |
|
269 | branch['name'] for branch in data['push']['branches']) | |
|
270 | ) | |
|
271 | else: | |
|
272 | if data['push']['branches']: | |
|
273 | _subject = '[{repo_name}] {author} pushed {commit_num} commits on branches: {branches}' | |
|
290 | if commit_num == 1: | |
|
291 | if data['push']['branches']: | |
|
292 | _subject = '[{repo_name}] {author} pushed {commit_num} commit on branches: {branches}' | |
|
293 | else: | |
|
294 | _subject = '[{repo_name}] {author} pushed {commit_num} commit' | |
|
295 | subject = _subject.format( | |
|
296 | author=data['actor']['username'], | |
|
297 | repo_name=data['repo']['repo_name'], | |
|
298 | commit_num=commit_num, | |
|
299 | branches=', '.join( | |
|
300 | branch['name'] for branch in data['push']['branches']) | |
|
301 | ) | |
|
274 | 302 | else: |
|
275 | _subject = '[{repo_name}] {author} pushed {commit_num} commits' | |
|
276 | subject = _subject.format( | |
|
277 | author=data['actor']['username'], | |
|
278 | repo_name=data['repo']['repo_name'], | |
|
279 | commit_num=commit_num, | |
|
280 | branches=', '.join( | |
|
281 | branch['name'] for branch in data['push']['branches'])) | |
|
303 | if data['push']['branches']: | |
|
304 | _subject = '[{repo_name}] {author} pushed {commit_num} commits on branches: {branches}' | |
|
305 | else: | |
|
306 | _subject = '[{repo_name}] {author} pushed {commit_num} commits' | |
|
307 | subject = _subject.format( | |
|
308 | author=data['actor']['username'], | |
|
309 | repo_name=data['repo']['repo_name'], | |
|
310 | commit_num=commit_num, | |
|
311 | branches=', '.join( | |
|
312 | branch['name'] for branch in data['push']['branches'])) | |
|
282 | 313 | |
|
283 | email_body_plaintext = render_with_traceback( | |
|
284 | REPO_PUSH_TEMPLATE_PLAINTEXT, | |
|
285 | data=data, | |
|
286 | subject=subject, | |
|
287 | instance_url=server_url) | |
|
314 | email_body_plaintext = render_with_traceback( | |
|
315 | REPO_PUSH_TEMPLATE_PLAINTEXT, | |
|
316 | data=data, | |
|
317 | subject=subject, | |
|
318 | instance_url=server_url) | |
|
288 | 319 | |
|
289 | email_body_html = render_with_traceback( | |
|
290 | REPO_PUSH_TEMPLATE_HTML, | |
|
291 | data=data, | |
|
292 | subject=subject, | |
|
293 | instance_url=server_url) | |
|
320 | email_body_html = render_with_traceback( | |
|
321 | REPO_PUSH_TEMPLATE_HTML, | |
|
322 | data=data, | |
|
323 | subject=subject, | |
|
324 | instance_url=server_url) | |
|
294 | 325 | |
|
295 |
|
|
|
296 | run_task( | |
|
297 | tasks.send_email, email_address, subject, | |
|
298 | email_body_plaintext, email_body_html) | |
|
326 | recipients = self.integration_settings['recipients'] | |
|
327 | for email_address in recipients: | |
|
328 | run_task( | |
|
329 | tasks.send_email, email_address, subject, | |
|
330 | email_body_plaintext, email_body_html) |
@@ -26,6 +26,7 b' import colander' | |||
|
26 | 26 | import textwrap |
|
27 | 27 | from mako.template import Template |
|
28 | 28 | from rhodecode import events |
|
29 | from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc | |
|
29 | 30 | from rhodecode.translation import _ |
|
30 | 31 | from rhodecode.lib import helpers as h |
|
31 | 32 | from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask |
@@ -119,13 +120,10 b' class HipchatIntegrationType(Integration' | |||
|
119 | 120 | |
|
120 | 121 | def send_event(self, event): |
|
121 | 122 | if event.__class__ not in self.valid_events: |
|
122 |
log.debug('event |
|
|
123 | log.debug('event %r not present in valid event list (%s)', event, self.valid_events) | |
|
123 | 124 | return |
|
124 | 125 | |
|
125 | allowed_events = self.settings['events'] | |
|
126 | if event.name not in allowed_events: | |
|
127 | log.debug('event ignored: %r event %s not in allowed events %s', | |
|
128 | event, event.name, allowed_events) | |
|
126 | if not self.event_enabled(event): | |
|
129 | 127 | return |
|
130 | 128 | |
|
131 | 129 | data = event.as_dict() |
@@ -133,8 +131,6 b' class HipchatIntegrationType(Integration' | |||
|
133 | 131 | text = '<b>%s<b> caused a <b>%s</b> event' % ( |
|
134 | 132 | data['actor']['username'], event.name) |
|
135 | 133 | |
|
136 | log.debug('handling hipchat event for %s', event.name) | |
|
137 | ||
|
138 | 134 | if isinstance(event, events.PullRequestCommentEvent): |
|
139 | 135 | text = self.format_pull_request_comment_event(event, data) |
|
140 | 136 | elif isinstance(event, events.PullRequestReviewEvent): |
@@ -154,12 +150,12 b' class HipchatIntegrationType(Integration' | |||
|
154 | 150 | schema = HipchatSettingsSchema() |
|
155 | 151 | schema.add(colander.SchemaNode( |
|
156 | 152 | colander.Set(), |
|
157 |
widget= |
|
|
153 | widget=CheckboxChoiceWidgetDesc( | |
|
158 | 154 | values=sorted( |
|
159 | [(e.name, e.display_name) for e in self.valid_events] | |
|
160 | ) | |
|
155 | [(e.name, e.display_name, e.description) for e in self.valid_events] | |
|
156 | ), | |
|
161 | 157 | ), |
|
162 |
description=" |
|
|
158 | description="List of events activated for this integration", | |
|
163 | 159 | name='events' |
|
164 | 160 | )) |
|
165 | 161 |
@@ -30,6 +30,7 b' import colander' | |||
|
30 | 30 | from mako.template import Template |
|
31 | 31 | |
|
32 | 32 | from rhodecode import events |
|
33 | from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc | |
|
33 | 34 | from rhodecode.translation import _ |
|
34 | 35 | from rhodecode.lib import helpers as h |
|
35 | 36 | from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask |
@@ -134,14 +135,13 b' class SlackIntegrationType(IntegrationTy' | |||
|
134 | 135 | ] |
|
135 | 136 | |
|
136 | 137 | def send_event(self, event): |
|
138 | log.debug('handling event %s with integration %s', event.name, self) | |
|
139 | ||
|
137 | 140 | if event.__class__ not in self.valid_events: |
|
138 |
log.debug('event |
|
|
141 | log.debug('event %r not present in valid event list (%s)', event, self.valid_events) | |
|
139 | 142 | return |
|
140 | 143 | |
|
141 | allowed_events = self.settings['events'] | |
|
142 | if event.name not in allowed_events: | |
|
143 | log.debug('event ignored: %r event %s not in allowed events %s', | |
|
144 | event, event.name, allowed_events) | |
|
144 | if not self.event_enabled(event): | |
|
145 | 145 | return |
|
146 | 146 | |
|
147 | 147 | data = event.as_dict() |
@@ -154,8 +154,6 b' class SlackIntegrationType(IntegrationTy' | |||
|
154 | 154 | fields = None |
|
155 | 155 | overrides = None |
|
156 | 156 | |
|
157 | log.debug('handling slack event for %s', event.name) | |
|
158 | ||
|
159 | 157 | if isinstance(event, events.PullRequestCommentEvent): |
|
160 | 158 | (title, text, fields, overrides) \ |
|
161 | 159 | = self.format_pull_request_comment_event(event, data) |
@@ -176,12 +174,12 b' class SlackIntegrationType(IntegrationTy' | |||
|
176 | 174 | schema = SlackSettingsSchema() |
|
177 | 175 | schema.add(colander.SchemaNode( |
|
178 | 176 | colander.Set(), |
|
179 |
widget= |
|
|
177 | widget=CheckboxChoiceWidgetDesc( | |
|
180 | 178 | values=sorted( |
|
181 | [(e.name, e.display_name) for e in self.valid_events] | |
|
182 | ) | |
|
179 | [(e.name, e.display_name, e.description) for e in self.valid_events] | |
|
180 | ), | |
|
183 | 181 | ), |
|
184 |
description=" |
|
|
182 | description="List of events activated for this integration", | |
|
185 | 183 | name='events' |
|
186 | 184 | )) |
|
187 | 185 |
@@ -20,13 +20,14 b'' | |||
|
20 | 20 | |
|
21 | 21 | from __future__ import unicode_literals |
|
22 | 22 | |
|
23 | import deform | |
|
24 | 23 | import deform.widget |
|
25 | 24 | import logging |
|
26 | 25 | import colander |
|
27 | 26 | |
|
28 | 27 | import rhodecode |
|
29 | 28 | from rhodecode import events |
|
29 | from rhodecode.lib.colander_utils import strip_whitespace | |
|
30 | from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc | |
|
30 | 31 | from rhodecode.translation import _ |
|
31 | 32 | from rhodecode.integrations.types.base import ( |
|
32 | 33 | IntegrationTypeBase, get_auth, get_web_token, get_url_vars, |
@@ -53,11 +54,12 b' class WebhookSettingsSchema(colander.Sch' | |||
|
53 | 54 | 'objects in data in such cases.'), |
|
54 | 55 | missing=colander.required, |
|
55 | 56 | required=True, |
|
57 | preparer=strip_whitespace, | |
|
56 | 58 | validator=colander.url, |
|
57 | 59 | widget=widgets.CodeMirrorWidget( |
|
58 | 60 | help_block_collapsable_name='Show url variables', |
|
59 | 61 | help_block_collapsable=( |
|
60 | 'E.g http://my-serv/trigger_job/${{event_name}}' | |
|
62 | 'E.g http://my-serv.com/trigger_job/${{event_name}}' | |
|
61 | 63 | '?PR_ID=${{pull_request_id}}' |
|
62 | 64 | '\nFull list of vars:\n{}'.format(URL_VARS)), |
|
63 | 65 | codemirror_mode='text', |
@@ -146,34 +148,31 b' class WebhookIntegrationType(Integration' | |||
|
146 | 148 | events.PullRequestCreateEvent, |
|
147 | 149 | events.RepoPushEvent, |
|
148 | 150 | events.RepoCreateEvent, |
|
151 | events.RepoCommitCommentEvent, | |
|
149 | 152 | ] |
|
150 | 153 | |
|
151 | 154 | def settings_schema(self): |
|
152 | 155 | schema = WebhookSettingsSchema() |
|
153 | 156 | schema.add(colander.SchemaNode( |
|
154 | 157 | colander.Set(), |
|
155 |
widget= |
|
|
158 | widget=CheckboxChoiceWidgetDesc( | |
|
156 | 159 | values=sorted( |
|
157 | [(e.name, e.display_name) for e in self.valid_events] | |
|
158 | ) | |
|
160 | [(e.name, e.display_name, e.description) for e in self.valid_events] | |
|
161 | ), | |
|
159 | 162 | ), |
|
160 |
description=" |
|
|
163 | description="List of events activated for this integration", | |
|
161 | 164 | name='events' |
|
162 | 165 | )) |
|
163 | 166 | return schema |
|
164 | 167 | |
|
165 | 168 | def send_event(self, event): |
|
166 | log.debug( | |
|
167 | 'handling event %s with Webhook integration %s', event.name, self) | |
|
169 | log.debug('handling event %s with integration %s', event.name, self) | |
|
168 | 170 | |
|
169 | 171 | if event.__class__ not in self.valid_events: |
|
170 |
log.debug('event |
|
|
172 | log.debug('event %r not present in valid event list (%s)', event, self.valid_events) | |
|
171 | 173 | return |
|
172 | 174 | |
|
173 | allowed_events = self.settings['events'] | |
|
174 | if event.name not in allowed_events: | |
|
175 | log.debug('event ignored: %r event %s not in allowed events %s', | |
|
176 | event, event.name, allowed_events) | |
|
175 | if not self.event_enabled(event): | |
|
177 | 176 | return |
|
178 | 177 | |
|
179 | 178 | data = event.as_dict() |
@@ -20,13 +20,40 b'' | |||
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | import deform | |
|
24 | 23 | import deform.widget |
|
24 | from deform.widget import null, OptGroup, string_types | |
|
25 | ||
|
26 | log = logging.getLogger(__name__) | |
|
25 | 27 | |
|
26 | 28 | |
|
27 | log = logging.getLogger(__name__) | |
|
29 | def _normalize_choices(values): | |
|
30 | result = [] | |
|
31 | for item in values: | |
|
32 | if isinstance(item, OptGroup): | |
|
33 | normalized_options = _normalize_choices(item.options) | |
|
34 | result.append(OptGroup(item.label, *normalized_options)) | |
|
35 | else: | |
|
36 | value, description, help_block = item | |
|
37 | if not isinstance(value, string_types): | |
|
38 | value = str(value) | |
|
39 | result.append((value, description, help_block)) | |
|
40 | return result | |
|
28 | 41 | |
|
29 | 42 | |
|
30 | 43 | class CodeMirrorWidget(deform.widget.TextAreaWidget): |
|
31 | 44 | template = 'codemirror' |
|
32 | 45 | requirements = (('deform', None), ('codemirror', None)) |
|
46 | ||
|
47 | ||
|
48 | class CheckboxChoiceWidgetDesc(deform.widget.CheckboxChoiceWidget): | |
|
49 | template = "checkbox_choice_desc" | |
|
50 | ||
|
51 | def serialize(self, field, cstruct, **kw): | |
|
52 | if cstruct in (null, None): | |
|
53 | cstruct = () | |
|
54 | readonly = kw.get("readonly", self.readonly) | |
|
55 | values = kw.get("values", self.values) | |
|
56 | kw["values"] = _normalize_choices(values) | |
|
57 | template = readonly and self.readonly_template or self.template | |
|
58 | tmpl_values = self.get_template_values(field, cstruct, kw) | |
|
59 | return field.renderer(template, **tmpl_values) |
@@ -22,4 +22,4 b'' | |||
|
22 | 22 | </div> |
|
23 | 23 | </div> |
|
24 | 24 | ${field.end_sequence()} |
|
25 | </div> No newline at end of file | |
|
25 | </div> |
@@ -40,7 +40,7 b' from rhodecode.events import (' | |||
|
40 | 40 | PullRequestUpdateEvent, |
|
41 | 41 | PullRequestReviewEvent, |
|
42 | 42 | PullRequestMergeEvent, |
|
43 |
PullRequestCloseEvent |
|
|
43 | PullRequestCloseEvent | |
|
44 | 44 | ]) |
|
45 | 45 | def test_pullrequest_events_serialized(EventClass, pr_util, config_stub): |
|
46 | 46 | pr = pr_util.create_pull_request() |
@@ -20,6 +20,7 b'' | |||
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | from rhodecode.lib.utils2 import StrictAttributeDict | |
|
23 | 24 | from rhodecode.tests.events.conftest import EventCatcher |
|
24 | 25 | |
|
25 | 26 | from rhodecode.lib import hooks_base, utils2 |
@@ -28,7 +29,7 b' from rhodecode.events.repo import (' | |||
|
28 | 29 | RepoPrePullEvent, RepoPullEvent, |
|
29 | 30 | RepoPrePushEvent, RepoPushEvent, |
|
30 | 31 | RepoPreCreateEvent, RepoCreateEvent, |
|
31 | RepoPreDeleteEvent, RepoDeleteEvent, | |
|
32 | RepoPreDeleteEvent, RepoDeleteEvent, RepoCommitCommentEvent, | |
|
32 | 33 | ) |
|
33 | 34 | |
|
34 | 35 | |
@@ -121,3 +122,24 b' def test_push_fires_events(scm_extras):' | |||
|
121 | 122 | hooks_base.post_pull(scm_extras) |
|
122 | 123 | assert event_catcher.events_types == [RepoPullEvent] |
|
123 | 124 | |
|
125 | ||
|
126 | @pytest.mark.parametrize('EventClass', [RepoCommitCommentEvent]) | |
|
127 | def test_repo_commit_event(config_stub, repo_stub, EventClass): | |
|
128 | ||
|
129 | commit = StrictAttributeDict({ | |
|
130 | 'raw_id': 'raw_id', | |
|
131 | 'message': 'message', | |
|
132 | 'branch': 'branch', | |
|
133 | }) | |
|
134 | ||
|
135 | comment = StrictAttributeDict({ | |
|
136 | 'comment_id': 'comment_id', | |
|
137 | 'text': 'text', | |
|
138 | 'comment_type': 'comment_type', | |
|
139 | 'f_path': 'f_path', | |
|
140 | 'line_no': 'line_no', | |
|
141 | }) | |
|
142 | event = EventClass(repo=repo_stub, commit=commit, comment=comment) | |
|
143 | data = event.as_dict() | |
|
144 | assert data['commit']['commit_id'] | |
|
145 | assert data['comment']['comment_id'] |
@@ -49,6 +49,7 b' class TestDeleteScopesDeletesIntegration' | |||
|
49 | 49 | |
|
50 | 50 | count = 1 |
|
51 | 51 | |
|
52 | ||
|
52 | 53 | def counter(): |
|
53 | 54 | global count |
|
54 | 55 | val = count |
@@ -52,8 +52,7 b' def test_webhook_parse_url_invalid_event' | |||
|
52 | 52 | handler(event, {}) |
|
53 | 53 | |
|
54 | 54 | err = str(err.value) |
|
55 | assert err.startswith( | |
|
56 | 'event type `%s` not in supported list' % event.__class__) | |
|
55 | assert err == "event type `<class 'rhodecode.events.repo.RepoDeleteEvent'>` has no handler defined" | |
|
57 | 56 | |
|
58 | 57 | |
|
59 | 58 | @pytest.mark.parametrize('template,expected_urls', [ |
General Comments 0
You need to be logged in to leave comments.
Login now