Show More
@@ -0,0 +1,17 b'' | |||
|
1 | # Copyright (C) 2012-2023 RhodeCode GmbH | |
|
2 | # | |
|
3 | # This program is free software: you can redistribute it and/or modify | |
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
5 | # (only), as published by the Free Software Foundation. | |
|
6 | # | |
|
7 | # This program is distributed in the hope that it will be useful, | |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
10 | # GNU General Public License for more details. | |
|
11 | # | |
|
12 | # You should have received a copy of the GNU Affero General Public License | |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
14 | # | |
|
15 | # This program is dual-licensed. If you wish to learn more about the | |
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
@@ -0,0 +1,242 b'' | |||
|
1 | # Copyright (C) 2012-2023 RhodeCode GmbH | |
|
2 | # | |
|
3 | # This program is free software: you can redistribute it and/or modify | |
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
5 | # (only), as published by the Free Software Foundation. | |
|
6 | # | |
|
7 | # This program is distributed in the hope that it will be useful, | |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
10 | # GNU General Public License for more details. | |
|
11 | # | |
|
12 | # You should have received a copy of the GNU Affero General Public License | |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
14 | # | |
|
15 | # This program is dual-licensed. If you wish to learn more about the | |
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
18 | import re | |
|
19 | import textwrap | |
|
20 | import dataclasses | |
|
21 | import logging | |
|
22 | import typing | |
|
23 | ||
|
24 | from mako.template import Template | |
|
25 | ||
|
26 | from rhodecode import events | |
|
27 | from rhodecode.integrations.types.base import CommitParsingDataHandler, render_with_traceback | |
|
28 | ||
|
29 | log = logging.getLogger(__name__) | |
|
30 | ||
|
31 | ||
|
32 | @dataclasses.dataclass | |
|
33 | class SlackData: | |
|
34 | title: str | |
|
35 | text: str | |
|
36 | fields: list[dict] | None = None | |
|
37 | overrides: dict | None = None | |
|
38 | ||
|
39 | ||
|
40 | def html_to_slack_links(message): | |
|
41 | return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(r'<\1|\2>', message) | |
|
42 | ||
|
43 | ||
|
44 | REPO_PUSH_TEMPLATE = Template(''' | |
|
45 | <% | |
|
46 | def branch_text(branch): | |
|
47 | if branch: | |
|
48 | return 'on branch: <{}|{}>'.format(branch_commits['branch']['url'], branch_commits['branch']['name']) | |
|
49 | else: | |
|
50 | ## case for SVN no branch push... | |
|
51 | return 'to trunk' | |
|
52 | %> \ | |
|
53 | ||
|
54 | % for branch, branch_commits in branches_commits.items(): | |
|
55 | ${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} ${branch_text(branch)} | |
|
56 | % for commit in branch_commits['commits']: | |
|
57 | `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html']|html_to_slack_links} | |
|
58 | % endfor | |
|
59 | % endfor | |
|
60 | ''') | |
|
61 | ||
|
62 | ||
|
63 | class SlackDataHandler(CommitParsingDataHandler): | |
|
64 | name = 'slack' | |
|
65 | ||
|
66 | def __init__(self): | |
|
67 | pass | |
|
68 | ||
|
69 | def __call__(self, event: events.RhodecodeEvent, data): | |
|
70 | if not isinstance(event, events.RhodecodeEvent): | |
|
71 | raise TypeError(f"event {event} is not subtype of events.RhodecodeEvent") | |
|
72 | ||
|
73 | actor = data["actor"]["username"] | |
|
74 | default_title = f'*{actor}* caused a *{event.name}* event' | |
|
75 | default_text = f'*{actor}* caused a *{event.name}* event' | |
|
76 | ||
|
77 | default_slack_data = SlackData(title=default_title, text=default_text) | |
|
78 | ||
|
79 | if isinstance(event, events.PullRequestCommentEvent): | |
|
80 | return self.format_pull_request_comment_event( | |
|
81 | event, data, default_slack_data | |
|
82 | ) | |
|
83 | elif isinstance(event, events.PullRequestCommentEditEvent): | |
|
84 | return self.format_pull_request_comment_event( | |
|
85 | event, data, default_slack_data | |
|
86 | ) | |
|
87 | elif isinstance(event, events.PullRequestReviewEvent): | |
|
88 | return self.format_pull_request_review_event(event, data, default_slack_data) | |
|
89 | elif isinstance(event, events.PullRequestEvent): | |
|
90 | return self.format_pull_request_event(event, data, default_slack_data) | |
|
91 | elif isinstance(event, events.RepoPushEvent): | |
|
92 | return self.format_repo_push_event(event, data, default_slack_data) | |
|
93 | elif isinstance(event, events.RepoCreateEvent): | |
|
94 | return self.format_repo_create_event(event, data, default_slack_data) | |
|
95 | else: | |
|
96 | raise ValueError( | |
|
97 | f'event type `{event.__class__}` has no handler defined') | |
|
98 | ||
|
99 | def format_pull_request_comment_event(self, event, data, slack_data): | |
|
100 | comment_text = data['comment']['text'] | |
|
101 | if len(comment_text) > 200: | |
|
102 | comment_text = '<{comment_url}|{comment_text}...>'.format( | |
|
103 | comment_text=comment_text[:200], | |
|
104 | comment_url=data['comment']['url'], | |
|
105 | ) | |
|
106 | ||
|
107 | fields = None | |
|
108 | overrides = None | |
|
109 | status_text = None | |
|
110 | ||
|
111 | if data['comment']['status']: | |
|
112 | status_color = { | |
|
113 | 'approved': '#0ac878', | |
|
114 | 'rejected': '#e85e4d'}.get(data['comment']['status']) | |
|
115 | ||
|
116 | if status_color: | |
|
117 | overrides = {"color": status_color} | |
|
118 | ||
|
119 | status_text = data['comment']['status'] | |
|
120 | ||
|
121 | if data['comment']['file']: | |
|
122 | fields = [ | |
|
123 | { | |
|
124 | "title": "file", | |
|
125 | "value": data['comment']['file'] | |
|
126 | }, | |
|
127 | { | |
|
128 | "title": "line", | |
|
129 | "value": data['comment']['line'] | |
|
130 | } | |
|
131 | ] | |
|
132 | ||
|
133 | template = Template(textwrap.dedent(r''' | |
|
134 | *${data['actor']['username']}* left ${data['comment']['type']} on pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>: | |
|
135 | ''')) | |
|
136 | title = render_with_traceback( | |
|
137 | template, data=data, comment=event.comment) | |
|
138 | ||
|
139 | template = Template(textwrap.dedent(r''' | |
|
140 | *pull request title*: ${pr_title} | |
|
141 | % if status_text: | |
|
142 | *submitted status*: `${status_text}` | |
|
143 | % endif | |
|
144 | >>> ${comment_text} | |
|
145 | ''')) | |
|
146 | text = render_with_traceback( | |
|
147 | template, | |
|
148 | comment_text=comment_text, | |
|
149 | pr_title=data['pullrequest']['title'], | |
|
150 | status_text=status_text) | |
|
151 | ||
|
152 | slack_data.title = title | |
|
153 | slack_data.text = text | |
|
154 | slack_data.fields = fields | |
|
155 | slack_data.overrides = overrides | |
|
156 | ||
|
157 | return slack_data | |
|
158 | ||
|
159 | def format_pull_request_review_event(self, event, data, slack_data) -> SlackData: | |
|
160 | template = Template(textwrap.dedent(r''' | |
|
161 | *${data['actor']['username']}* changed status of pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']} to `${data['pullrequest']['status']}`>: | |
|
162 | ''')) | |
|
163 | title = render_with_traceback(template, data=data) | |
|
164 | ||
|
165 | template = Template(textwrap.dedent(r''' | |
|
166 | *pull request title*: ${pr_title} | |
|
167 | ''')) | |
|
168 | text = render_with_traceback( | |
|
169 | template, | |
|
170 | pr_title=data['pullrequest']['title']) | |
|
171 | ||
|
172 | slack_data.title = title | |
|
173 | slack_data.text = text | |
|
174 | ||
|
175 | return slack_data | |
|
176 | ||
|
177 | def format_pull_request_event(self, event, data, slack_data) -> SlackData: | |
|
178 | action = { | |
|
179 | events.PullRequestCloseEvent: 'closed', | |
|
180 | events.PullRequestMergeEvent: 'merged', | |
|
181 | events.PullRequestUpdateEvent: 'updated', | |
|
182 | events.PullRequestCreateEvent: 'created', | |
|
183 | }.get(event.__class__, str(event.__class__)) | |
|
184 | ||
|
185 | template = Template(textwrap.dedent(r''' | |
|
186 | *${data['actor']['username']}* `${action}` pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>: | |
|
187 | ''')) | |
|
188 | title = render_with_traceback(template, data=data, action=action) | |
|
189 | ||
|
190 | template = Template(textwrap.dedent(r''' | |
|
191 | *pull request title*: ${pr_title} | |
|
192 | %if data['pullrequest']['commits']: | |
|
193 | *commits*: ${len(data['pullrequest']['commits'])} | |
|
194 | %endif | |
|
195 | ''')) | |
|
196 | text = render_with_traceback( | |
|
197 | template, | |
|
198 | pr_title=data['pullrequest']['title'], | |
|
199 | data=data) | |
|
200 | ||
|
201 | slack_data.title = title | |
|
202 | slack_data.text = text | |
|
203 | ||
|
204 | return slack_data | |
|
205 | ||
|
206 | def format_repo_push_event(self, event, data, slack_data) -> SlackData: | |
|
207 | branches_commits = self.aggregate_branch_data( | |
|
208 | data['push']['branches'], data['push']['commits']) | |
|
209 | ||
|
210 | template = Template(r''' | |
|
211 | *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>: | |
|
212 | ''') | |
|
213 | title = render_with_traceback(template, data=data) | |
|
214 | ||
|
215 | text = render_with_traceback( | |
|
216 | REPO_PUSH_TEMPLATE, | |
|
217 | data=data, | |
|
218 | branches_commits=branches_commits, | |
|
219 | html_to_slack_links=html_to_slack_links, | |
|
220 | ) | |
|
221 | ||
|
222 | slack_data.title = title | |
|
223 | slack_data.text = text | |
|
224 | ||
|
225 | return slack_data | |
|
226 | ||
|
227 | def format_repo_create_event(self, event, data, slack_data) -> SlackData: | |
|
228 | template = Template(r''' | |
|
229 | *${data['actor']['username']}* created new repository ${data['repo']['repo_name']}: | |
|
230 | ''') | |
|
231 | title = render_with_traceback(template, data=data) | |
|
232 | ||
|
233 | template = Template(textwrap.dedent(r''' | |
|
234 | repo_url: ${data['repo']['url']} | |
|
235 | repo_type: ${data['repo']['repo_type']} | |
|
236 | ''')) | |
|
237 | text = render_with_traceback(template, data=data) | |
|
238 | ||
|
239 | slack_data.title = title | |
|
240 | slack_data.text = text | |
|
241 | ||
|
242 | return slack_data No newline at end of file |
@@ -0,0 +1,175 b'' | |||
|
1 | # Copyright (C) 2012-2023 RhodeCode GmbH | |
|
2 | # | |
|
3 | # This program is free software: you can redistribute it and/or modify | |
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
5 | # (only), as published by the Free Software Foundation. | |
|
6 | # | |
|
7 | # This program is distributed in the hope that it will be useful, | |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
10 | # GNU General Public License for more details. | |
|
11 | # | |
|
12 | # You should have received a copy of the GNU Affero General Public License | |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
14 | # | |
|
15 | # This program is dual-licensed. If you wish to learn more about the | |
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
18 | ||
|
19 | import logging | |
|
20 | ||
|
21 | from rhodecode import events | |
|
22 | from rhodecode.integrations.types.base import CommitParsingDataHandler, UrlTmpl | |
|
23 | ||
|
24 | ||
|
25 | log = logging.getLogger(__name__) | |
|
26 | ||
|
27 | ||
|
28 | class WebhookDataHandler(CommitParsingDataHandler): | |
|
29 | name = 'webhook' | |
|
30 | ||
|
31 | def __init__(self, template_url, headers): | |
|
32 | self.template_url = template_url | |
|
33 | self.headers = headers | |
|
34 | ||
|
35 | def get_base_parsed_template(self, data): | |
|
36 | """ | |
|
37 | initially parses the passed in template with some common variables | |
|
38 | available on ALL calls | |
|
39 | """ | |
|
40 | # note: make sure to update the `WEBHOOK_URL_VARS` if this changes | |
|
41 | common_vars = { | |
|
42 | 'repo_name': data['repo']['repo_name'], | |
|
43 | 'repo_type': data['repo']['repo_type'], | |
|
44 | 'repo_id': data['repo']['repo_id'], | |
|
45 | 'repo_url': data['repo']['url'], | |
|
46 | 'username': data['actor']['username'], | |
|
47 | 'user_id': data['actor']['user_id'], | |
|
48 | 'event_name': data['name'] | |
|
49 | } | |
|
50 | ||
|
51 | extra_vars = {} | |
|
52 | for extra_key, extra_val in data['repo']['extra_fields'].items(): | |
|
53 | extra_vars[f'extra__{extra_key}'] = extra_val | |
|
54 | common_vars.update(extra_vars) | |
|
55 | ||
|
56 | template_url = self.template_url.replace('${extra:', '${extra__') | |
|
57 | for k, v in common_vars.items(): | |
|
58 | template_url = UrlTmpl(template_url).safe_substitute(**{k: v}) | |
|
59 | return template_url | |
|
60 | ||
|
61 | def repo_push_event_handler(self, event, data): | |
|
62 | url = self.get_base_parsed_template(data) | |
|
63 | url_calls = [] | |
|
64 | ||
|
65 | branches_commits = self.aggregate_branch_data( | |
|
66 | data['push']['branches'], data['push']['commits']) | |
|
67 | if '${branch}' in url or '${branch_head}' in url or '${commit_id}' in url: | |
|
68 | # call it multiple times, for each branch if used in variables | |
|
69 | for branch, commit_ids in branches_commits.items(): | |
|
70 | branch_url = UrlTmpl(url).safe_substitute(branch=branch) | |
|
71 | ||
|
72 | if '${branch_head}' in branch_url: | |
|
73 | # last commit in the aggregate is the head of the branch | |
|
74 | branch_head = commit_ids['branch_head'] | |
|
75 | branch_url = UrlTmpl(branch_url).safe_substitute(branch_head=branch_head) | |
|
76 | ||
|
77 | # call further down for each commit if used | |
|
78 | if '${commit_id}' in branch_url: | |
|
79 | for commit_data in commit_ids['commits']: | |
|
80 | commit_id = commit_data['raw_id'] | |
|
81 | commit_url = UrlTmpl(branch_url).safe_substitute(commit_id=commit_id) | |
|
82 | # register per-commit call | |
|
83 | log.debug( | |
|
84 | 'register %s call(%s) to url %s', | |
|
85 | self.name, event, commit_url) | |
|
86 | url_calls.append( | |
|
87 | (commit_url, self.headers, data)) | |
|
88 | ||
|
89 | else: | |
|
90 | # register per-branch call | |
|
91 | log.debug('register %s call(%s) to url %s', | |
|
92 | self.name, event, branch_url) | |
|
93 | url_calls.append((branch_url, self.headers, data)) | |
|
94 | ||
|
95 | else: | |
|
96 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
97 | url_calls.append((url, self.headers, data)) | |
|
98 | ||
|
99 | return url_calls | |
|
100 | ||
|
101 | def repo_commit_comment_handler(self, event, data): | |
|
102 | url = self.get_base_parsed_template(data) | |
|
103 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
104 | comment_vars = [ | |
|
105 | ('commit_comment_id', data['comment']['comment_id']), | |
|
106 | ('commit_comment_text', data['comment']['comment_text']), | |
|
107 | ('commit_comment_type', data['comment']['comment_type']), | |
|
108 | ||
|
109 | ('commit_comment_f_path', data['comment']['comment_f_path']), | |
|
110 | ('commit_comment_line_no', data['comment']['comment_line_no']), | |
|
111 | ||
|
112 | ('commit_comment_commit_id', data['commit']['commit_id']), | |
|
113 | ('commit_comment_commit_branch', data['commit']['commit_branch']), | |
|
114 | ('commit_comment_commit_message', data['commit']['commit_message']), | |
|
115 | ] | |
|
116 | for k, v in comment_vars: | |
|
117 | url = UrlTmpl(url).safe_substitute(**{k: v}) | |
|
118 | ||
|
119 | return [(url, self.headers, data)] | |
|
120 | ||
|
121 | def repo_commit_comment_edit_handler(self, event, data): | |
|
122 | url = self.get_base_parsed_template(data) | |
|
123 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
124 | comment_vars = [ | |
|
125 | ('commit_comment_id', data['comment']['comment_id']), | |
|
126 | ('commit_comment_text', data['comment']['comment_text']), | |
|
127 | ('commit_comment_type', data['comment']['comment_type']), | |
|
128 | ||
|
129 | ('commit_comment_f_path', data['comment']['comment_f_path']), | |
|
130 | ('commit_comment_line_no', data['comment']['comment_line_no']), | |
|
131 | ||
|
132 | ('commit_comment_commit_id', data['commit']['commit_id']), | |
|
133 | ('commit_comment_commit_branch', data['commit']['commit_branch']), | |
|
134 | ('commit_comment_commit_message', data['commit']['commit_message']), | |
|
135 | ] | |
|
136 | for k, v in comment_vars: | |
|
137 | url = UrlTmpl(url).safe_substitute(**{k: v}) | |
|
138 | ||
|
139 | return [(url, self.headers, data)] | |
|
140 | ||
|
141 | def repo_create_event_handler(self, event, data): | |
|
142 | url = self.get_base_parsed_template(data) | |
|
143 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
144 | return [(url, self.headers, data)] | |
|
145 | ||
|
146 | def pull_request_event_handler(self, event, data): | |
|
147 | url = self.get_base_parsed_template(data) | |
|
148 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
149 | pr_vars = [ | |
|
150 | ('pull_request_id', data['pullrequest']['pull_request_id']), | |
|
151 | ('pull_request_title', data['pullrequest']['title']), | |
|
152 | ('pull_request_url', data['pullrequest']['url']), | |
|
153 | ('pull_request_shadow_url', data['pullrequest']['shadow_url']), | |
|
154 | ('pull_request_commits_uid', data['pullrequest']['commits_uid']), | |
|
155 | ] | |
|
156 | for k, v in pr_vars: | |
|
157 | url = UrlTmpl(url).safe_substitute(**{k: v}) | |
|
158 | ||
|
159 | return [(url, self.headers, data)] | |
|
160 | ||
|
161 | def __call__(self, event, data): | |
|
162 | ||
|
163 | if isinstance(event, events.RepoPushEvent): | |
|
164 | return self.repo_push_event_handler(event, data) | |
|
165 | elif isinstance(event, events.RepoCreateEvent): | |
|
166 | return self.repo_create_event_handler(event, data) | |
|
167 | elif isinstance(event, events.RepoCommitCommentEvent): | |
|
168 | return self.repo_commit_comment_handler(event, data) | |
|
169 | elif isinstance(event, events.RepoCommitCommentEditEvent): | |
|
170 | return self.repo_commit_comment_edit_handler(event, data) | |
|
171 | elif isinstance(event, events.PullRequestEvent): | |
|
172 | return self.pull_request_event_handler(event, data) | |
|
173 | else: | |
|
174 | raise ValueError( | |
|
175 | f'event type `{event.__class__}` has no handler defined') |
@@ -18,7 +18,10 b'' | |||
|
18 | 18 | |
|
19 | 19 | import logging |
|
20 | 20 | |
|
21 |
from rhodecode.events.base import |
|
|
21 | from rhodecode.events.base import ( | |
|
22 | RhodeCodeIntegrationEvent, | |
|
23 | RhodecodeEvent | |
|
24 | ) | |
|
22 | 25 | |
|
23 | 26 | from rhodecode.events.base import ( # pragma: no cover |
|
24 | 27 | FtsBuild |
@@ -17,6 +17,7 b'' | |||
|
17 | 17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
18 | 18 | import logging |
|
19 | 19 | import datetime |
|
20 | import typing | |
|
20 | 21 | |
|
21 | 22 | from zope.cachedescriptors.property import Lazy as LazyProperty |
|
22 | 23 | from pyramid.threadlocal import get_current_request |
@@ -19,7 +19,7 b' import sys' | |||
|
19 | 19 | import logging |
|
20 | 20 | |
|
21 | 21 | from rhodecode.integrations.registry import IntegrationTypeRegistry |
|
22 |
from rhodecode.integrations.types import webhook, slack, |
|
|
22 | from rhodecode.integrations.types import webhook, slack, email, base | |
|
23 | 23 | from rhodecode.lib.exc_tracking import store_exception |
|
24 | 24 | |
|
25 | 25 | log = logging.getLogger(__name__) |
@@ -35,8 +35,6 b' integration_type_registry.register_integ' | |||
|
35 | 35 | integration_type_registry.register_integration_type( |
|
36 | 36 | slack.SlackIntegrationType) |
|
37 | 37 | integration_type_registry.register_integration_type( |
|
38 | hipchat.HipchatIntegrationType) | |
|
39 | integration_type_registry.register_integration_type( | |
|
40 | 38 | email.EmailIntegrationType) |
|
41 | 39 | |
|
42 | 40 |
@@ -20,17 +20,18 b' import colander' | |||
|
20 | 20 | import string |
|
21 | 21 | import collections |
|
22 | 22 | import logging |
|
23 | ||
|
23 | 24 | import requests |
|
24 | 25 | import urllib.request |
|
25 | 26 | import urllib.parse |
|
26 | 27 | import urllib.error |
|
27 | 28 | from requests.adapters import HTTPAdapter |
|
28 |
from requests.packages.urllib3.util |
|
|
29 | from requests.packages.urllib3.util import Retry | |
|
29 | 30 | |
|
30 | 31 | from mako import exceptions |
|
31 | 32 | |
|
32 | from rhodecode.lib.utils2 import safe_str | |
|
33 |
from rhodecode. |
|
|
33 | ||
|
34 | from rhodecode.lib.str_utils import safe_str | |
|
34 | 35 | |
|
35 | 36 | |
|
36 | 37 | log = logging.getLogger(__name__) |
@@ -238,157 +239,6 b' class CommitParsingDataHandler(object):' | |||
|
238 | 239 | return branches_commits |
|
239 | 240 | |
|
240 | 241 | |
|
241 | class WebhookDataHandler(CommitParsingDataHandler): | |
|
242 | name = 'webhook' | |
|
243 | ||
|
244 | def __init__(self, template_url, headers): | |
|
245 | self.template_url = template_url | |
|
246 | self.headers = headers | |
|
247 | ||
|
248 | def get_base_parsed_template(self, data): | |
|
249 | """ | |
|
250 | initially parses the passed in template with some common variables | |
|
251 | available on ALL calls | |
|
252 | """ | |
|
253 | # note: make sure to update the `WEBHOOK_URL_VARS` if this changes | |
|
254 | common_vars = { | |
|
255 | 'repo_name': data['repo']['repo_name'], | |
|
256 | 'repo_type': data['repo']['repo_type'], | |
|
257 | 'repo_id': data['repo']['repo_id'], | |
|
258 | 'repo_url': data['repo']['url'], | |
|
259 | 'username': data['actor']['username'], | |
|
260 | 'user_id': data['actor']['user_id'], | |
|
261 | 'event_name': data['name'] | |
|
262 | } | |
|
263 | ||
|
264 | extra_vars = {} | |
|
265 | for extra_key, extra_val in data['repo']['extra_fields'].items(): | |
|
266 | extra_vars[f'extra__{extra_key}'] = extra_val | |
|
267 | common_vars.update(extra_vars) | |
|
268 | ||
|
269 | template_url = self.template_url.replace('${extra:', '${extra__') | |
|
270 | for k, v in common_vars.items(): | |
|
271 | template_url = UrlTmpl(template_url).safe_substitute(**{k: v}) | |
|
272 | return template_url | |
|
273 | ||
|
274 | def repo_push_event_handler(self, event, data): | |
|
275 | url = self.get_base_parsed_template(data) | |
|
276 | url_calls = [] | |
|
277 | ||
|
278 | branches_commits = self.aggregate_branch_data( | |
|
279 | data['push']['branches'], data['push']['commits']) | |
|
280 | if '${branch}' in url or '${branch_head}' in url or '${commit_id}' in url: | |
|
281 | # call it multiple times, for each branch if used in variables | |
|
282 | for branch, commit_ids in branches_commits.items(): | |
|
283 | branch_url = UrlTmpl(url).safe_substitute(branch=branch) | |
|
284 | ||
|
285 | if '${branch_head}' in branch_url: | |
|
286 | # last commit in the aggregate is the head of the branch | |
|
287 | branch_head = commit_ids['branch_head'] | |
|
288 | branch_url = UrlTmpl(branch_url).safe_substitute(branch_head=branch_head) | |
|
289 | ||
|
290 | # call further down for each commit if used | |
|
291 | if '${commit_id}' in branch_url: | |
|
292 | for commit_data in commit_ids['commits']: | |
|
293 | commit_id = commit_data['raw_id'] | |
|
294 | commit_url = UrlTmpl(branch_url).safe_substitute(commit_id=commit_id) | |
|
295 | # register per-commit call | |
|
296 | log.debug( | |
|
297 | 'register %s call(%s) to url %s', | |
|
298 | self.name, event, commit_url) | |
|
299 | url_calls.append( | |
|
300 | (commit_url, self.headers, data)) | |
|
301 | ||
|
302 | else: | |
|
303 | # register per-branch call | |
|
304 | log.debug('register %s call(%s) to url %s', | |
|
305 | self.name, event, branch_url) | |
|
306 | url_calls.append((branch_url, self.headers, data)) | |
|
307 | ||
|
308 | else: | |
|
309 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
310 | url_calls.append((url, self.headers, data)) | |
|
311 | ||
|
312 | return url_calls | |
|
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 | ||
|
334 | def repo_commit_comment_edit_handler(self, event, data): | |
|
335 | url = self.get_base_parsed_template(data) | |
|
336 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
337 | comment_vars = [ | |
|
338 | ('commit_comment_id', data['comment']['comment_id']), | |
|
339 | ('commit_comment_text', data['comment']['comment_text']), | |
|
340 | ('commit_comment_type', data['comment']['comment_type']), | |
|
341 | ||
|
342 | ('commit_comment_f_path', data['comment']['comment_f_path']), | |
|
343 | ('commit_comment_line_no', data['comment']['comment_line_no']), | |
|
344 | ||
|
345 | ('commit_comment_commit_id', data['commit']['commit_id']), | |
|
346 | ('commit_comment_commit_branch', data['commit']['commit_branch']), | |
|
347 | ('commit_comment_commit_message', data['commit']['commit_message']), | |
|
348 | ] | |
|
349 | for k, v in comment_vars: | |
|
350 | url = UrlTmpl(url).safe_substitute(**{k: v}) | |
|
351 | ||
|
352 | return [(url, self.headers, data)] | |
|
353 | ||
|
354 | def repo_create_event_handler(self, event, data): | |
|
355 | url = self.get_base_parsed_template(data) | |
|
356 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
357 | return [(url, self.headers, data)] | |
|
358 | ||
|
359 | def pull_request_event_handler(self, event, data): | |
|
360 | url = self.get_base_parsed_template(data) | |
|
361 | log.debug('register %s call(%s) to url %s', self.name, event, url) | |
|
362 | pr_vars = [ | |
|
363 | ('pull_request_id', data['pullrequest']['pull_request_id']), | |
|
364 | ('pull_request_title', data['pullrequest']['title']), | |
|
365 | ('pull_request_url', data['pullrequest']['url']), | |
|
366 | ('pull_request_shadow_url', data['pullrequest']['shadow_url']), | |
|
367 | ('pull_request_commits_uid', data['pullrequest']['commits_uid']), | |
|
368 | ] | |
|
369 | for k, v in pr_vars: | |
|
370 | url = UrlTmpl(url).safe_substitute(**{k: v}) | |
|
371 | ||
|
372 | return [(url, self.headers, data)] | |
|
373 | ||
|
374 | def __call__(self, event, data): | |
|
375 | from rhodecode import events | |
|
376 | ||
|
377 | if isinstance(event, events.RepoPushEvent): | |
|
378 | return self.repo_push_event_handler(event, data) | |
|
379 | elif isinstance(event, events.RepoCreateEvent): | |
|
380 | return self.repo_create_event_handler(event, data) | |
|
381 | elif isinstance(event, events.RepoCommitCommentEvent): | |
|
382 | return self.repo_commit_comment_handler(event, data) | |
|
383 | elif isinstance(event, events.RepoCommitCommentEditEvent): | |
|
384 | return self.repo_commit_comment_edit_handler(event, data) | |
|
385 | elif isinstance(event, events.PullRequestEvent): | |
|
386 | return self.pull_request_event_handler(event, data) | |
|
387 | else: | |
|
388 | raise ValueError( | |
|
389 | f'event type `{event.__class__}` has no handler defined') | |
|
390 | ||
|
391 | ||
|
392 | 242 | def get_auth(settings): |
|
393 | 243 | from requests.auth import HTTPBasicAuth |
|
394 | 244 | username = settings.get('username') |
@@ -17,15 +17,13 b'' | |||
|
17 | 17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
18 | 18 | |
|
19 | 19 | |
|
20 | import re | |
|
20 | ||
|
21 | 21 | import time |
|
22 | import textwrap | |
|
23 | 22 | import logging |
|
24 | 23 | |
|
25 | import deform | |
|
26 | import requests | |
|
24 | import deform # noqa | |
|
25 | import deform.widget | |
|
27 | 26 | import colander |
|
28 | from mako.template import Template | |
|
29 | 27 | |
|
30 | 28 | from rhodecode import events |
|
31 | 29 | from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc |
@@ -34,34 +32,14 b' from rhodecode.lib import helpers as h' | |||
|
34 | 32 | from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask |
|
35 | 33 | from rhodecode.lib.colander_utils import strip_whitespace |
|
36 | 34 | from rhodecode.integrations.types.base import ( |
|
37 | IntegrationTypeBase, CommitParsingDataHandler, render_with_traceback, | |
|
38 |
requests_retry_call |
|
|
39 | ||
|
40 | log = logging.getLogger(__name__) | |
|
35 | IntegrationTypeBase, | |
|
36 | requests_retry_call, | |
|
37 | ) | |
|
41 | 38 | |
|
42 | ||
|
43 | def html_to_slack_links(message): | |
|
44 | return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub( | |
|
45 | r'<\1|\2>', message) | |
|
39 | from rhodecode.integrations.types.handlers.slack import SlackDataHandler, SlackData | |
|
46 | 40 | |
|
47 | 41 | |
|
48 | REPO_PUSH_TEMPLATE = Template(''' | |
|
49 | <% | |
|
50 | def branch_text(branch): | |
|
51 | if branch: | |
|
52 | return 'on branch: <{}|{}>'.format(branch_commits['branch']['url'], branch_commits['branch']['name']) | |
|
53 | else: | |
|
54 | ## case for SVN no branch push... | |
|
55 | return 'to trunk' | |
|
56 | %> \ | |
|
57 | ||
|
58 | % for branch, branch_commits in branches_commits.items(): | |
|
59 | ${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} ${branch_text(branch)} | |
|
60 | % for commit in branch_commits['commits']: | |
|
61 | `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html']|html_to_slack_links} | |
|
62 | % endfor | |
|
63 | % endfor | |
|
64 | ''') | |
|
42 | log = logging.getLogger(__name__) | |
|
65 | 43 | |
|
66 | 44 | |
|
67 | 45 | class SlackSettingsSchema(colander.Schema): |
@@ -111,7 +89,7 b' class SlackSettingsSchema(colander.Schem' | |||
|
111 | 89 | ) |
|
112 | 90 | |
|
113 | 91 | |
|
114 |
class SlackIntegrationType(IntegrationTypeBase |
|
|
92 | class SlackIntegrationType(IntegrationTypeBase): | |
|
115 | 93 | key = 'slack' |
|
116 | 94 | display_name = _('Slack') |
|
117 | 95 | description = _('Send events such as repo pushes and pull requests to ' |
@@ -132,45 +110,6 b' class SlackIntegrationType(IntegrationTy' | |||
|
132 | 110 | events.RepoCreateEvent, |
|
133 | 111 | ] |
|
134 | 112 | |
|
135 | def send_event(self, event): | |
|
136 | log.debug('handling event %s with integration %s', event.name, self) | |
|
137 | ||
|
138 | if event.__class__ not in self.valid_events: | |
|
139 | log.debug('event %r not present in valid event list (%s)', event, self.valid_events) | |
|
140 | return | |
|
141 | ||
|
142 | if not self.event_enabled(event): | |
|
143 | return | |
|
144 | ||
|
145 | data = event.as_dict() | |
|
146 | ||
|
147 | # defaults | |
|
148 | title = '*%s* caused a *%s* event' % ( | |
|
149 | data['actor']['username'], event.name) | |
|
150 | text = '*%s* caused a *%s* event' % ( | |
|
151 | data['actor']['username'], event.name) | |
|
152 | fields = None | |
|
153 | overrides = None | |
|
154 | ||
|
155 | if isinstance(event, events.PullRequestCommentEvent): | |
|
156 | (title, text, fields, overrides) \ | |
|
157 | = self.format_pull_request_comment_event(event, data) | |
|
158 | elif isinstance(event, events.PullRequestCommentEditEvent): | |
|
159 | (title, text, fields, overrides) \ | |
|
160 | = self.format_pull_request_comment_event(event, data) | |
|
161 | elif isinstance(event, events.PullRequestReviewEvent): | |
|
162 | title, text = self.format_pull_request_review_event(event, data) | |
|
163 | elif isinstance(event, events.PullRequestEvent): | |
|
164 | title, text = self.format_pull_request_event(event, data) | |
|
165 | elif isinstance(event, events.RepoPushEvent): | |
|
166 | title, text = self.format_repo_push_event(data) | |
|
167 | elif isinstance(event, events.RepoCreateEvent): | |
|
168 | title, text = self.format_repo_create_event(data) | |
|
169 | else: | |
|
170 | log.error('unhandled event type: %r', event) | |
|
171 | ||
|
172 | run_task(post_text_to_slack, self.settings, title, text, fields, overrides) | |
|
173 | ||
|
174 | 113 | def settings_schema(self): |
|
175 | 114 | schema = SlackSettingsSchema() |
|
176 | 115 | schema.add(colander.SchemaNode( |
@@ -186,137 +125,31 b' class SlackIntegrationType(IntegrationTy' | |||
|
186 | 125 | |
|
187 | 126 | return schema |
|
188 | 127 | |
|
189 |
def |
|
|
190 | comment_text = data['comment']['text'] | |
|
191 | if len(comment_text) > 200: | |
|
192 | comment_text = '<{comment_url}|{comment_text}...>'.format( | |
|
193 | comment_text=comment_text[:200], | |
|
194 | comment_url=data['comment']['url'], | |
|
195 | ) | |
|
196 | ||
|
197 | fields = None | |
|
198 | overrides = None | |
|
199 | status_text = None | |
|
200 | ||
|
201 | if data['comment']['status']: | |
|
202 | status_color = { | |
|
203 | 'approved': '#0ac878', | |
|
204 | 'rejected': '#e85e4d'}.get(data['comment']['status']) | |
|
205 | ||
|
206 | if status_color: | |
|
207 | overrides = {"color": status_color} | |
|
208 | ||
|
209 | status_text = data['comment']['status'] | |
|
128 | def send_event(self, event): | |
|
129 | log.debug('handling event %s with integration %s', event.name, self) | |
|
210 | 130 | |
|
211 | if data['comment']['file']: | |
|
212 | fields = [ | |
|
213 |
|
|
|
214 | "title": "file", | |
|
215 | "value": data['comment']['file'] | |
|
216 | }, | |
|
217 | { | |
|
218 | "title": "line", | |
|
219 | "value": data['comment']['line'] | |
|
220 | } | |
|
221 | ] | |
|
222 | ||
|
223 | template = Template(textwrap.dedent(r''' | |
|
224 | *${data['actor']['username']}* left ${data['comment']['type']} on pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>: | |
|
225 | ''')) | |
|
226 | title = render_with_traceback( | |
|
227 | template, data=data, comment=event.comment) | |
|
228 | ||
|
229 | template = Template(textwrap.dedent(r''' | |
|
230 | *pull request title*: ${pr_title} | |
|
231 | % if status_text: | |
|
232 | *submitted status*: `${status_text}` | |
|
233 | % endif | |
|
234 | >>> ${comment_text} | |
|
235 | ''')) | |
|
236 | text = render_with_traceback( | |
|
237 | template, | |
|
238 | comment_text=comment_text, | |
|
239 | pr_title=data['pullrequest']['title'], | |
|
240 | status_text=status_text) | |
|
241 | ||
|
242 | return title, text, fields, overrides | |
|
243 | ||
|
244 | def format_pull_request_review_event(self, event, data): | |
|
245 | template = Template(textwrap.dedent(r''' | |
|
246 | *${data['actor']['username']}* changed status of pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']} to `${data['pullrequest']['status']}`>: | |
|
247 | ''')) | |
|
248 | title = render_with_traceback(template, data=data) | |
|
131 | if event.__class__ not in self.valid_events: | |
|
132 | log.debug('event %r not present in valid event list (%s)', event, self.valid_events) | |
|
133 | return | |
|
249 | 134 | |
|
250 | template = Template(textwrap.dedent(r''' | |
|
251 | *pull request title*: ${pr_title} | |
|
252 | ''')) | |
|
253 | text = render_with_traceback( | |
|
254 | template, | |
|
255 | pr_title=data['pullrequest']['title']) | |
|
256 | ||
|
257 | return title, text | |
|
135 | if not self.event_enabled(event): | |
|
136 | return | |
|
258 | 137 | |
|
259 | def format_pull_request_event(self, event, data): | |
|
260 | action = { | |
|
261 | events.PullRequestCloseEvent: 'closed', | |
|
262 | events.PullRequestMergeEvent: 'merged', | |
|
263 | events.PullRequestUpdateEvent: 'updated', | |
|
264 | events.PullRequestCreateEvent: 'created', | |
|
265 | }.get(event.__class__, str(event.__class__)) | |
|
266 | ||
|
267 | template = Template(textwrap.dedent(r''' | |
|
268 | *${data['actor']['username']}* `${action}` pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>: | |
|
269 | ''')) | |
|
270 | title = render_with_traceback(template, data=data, action=action) | |
|
271 | ||
|
272 | template = Template(textwrap.dedent(r''' | |
|
273 | *pull request title*: ${pr_title} | |
|
274 | %if data['pullrequest']['commits']: | |
|
275 | *commits*: ${len(data['pullrequest']['commits'])} | |
|
276 | %endif | |
|
277 | ''')) | |
|
278 | text = render_with_traceback( | |
|
279 | template, | |
|
280 | pr_title=data['pullrequest']['title'], | |
|
281 | data=data) | |
|
138 | data = event.as_dict() | |
|
282 | 139 | |
|
283 | return title, text | |
|
284 | ||
|
285 | def format_repo_push_event(self, data): | |
|
286 | branches_commits = self.aggregate_branch_data( | |
|
287 | data['push']['branches'], data['push']['commits']) | |
|
288 | ||
|
289 | template = Template(r''' | |
|
290 | *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>: | |
|
291 | ''') | |
|
292 | title = render_with_traceback(template, data=data) | |
|
293 | ||
|
294 | text = render_with_traceback( | |
|
295 | REPO_PUSH_TEMPLATE, | |
|
296 | data=data, | |
|
297 | branches_commits=branches_commits, | |
|
298 | html_to_slack_links=html_to_slack_links, | |
|
299 | ) | |
|
300 | ||
|
301 | return title, text | |
|
302 | ||
|
303 | def format_repo_create_event(self, data): | |
|
304 | template = Template(r''' | |
|
305 | *${data['actor']['username']}* created new repository ${data['repo']['repo_name']}: | |
|
306 | ''') | |
|
307 | title = render_with_traceback(template, data=data) | |
|
308 | ||
|
309 | template = Template(textwrap.dedent(r''' | |
|
310 | repo_url: ${data['repo']['url']} | |
|
311 | repo_type: ${data['repo']['repo_type']} | |
|
312 | ''')) | |
|
313 | text = render_with_traceback(template, data=data) | |
|
314 | ||
|
315 | return title, text | |
|
140 | handler = SlackDataHandler() | |
|
141 | slack_data = handler(event, data) | |
|
142 | # title, text, fields, overrides | |
|
143 | run_task(post_text_to_slack, self.settings, slack_data) | |
|
316 | 144 | |
|
317 | 145 | |
|
318 | 146 | @async_task(ignore_result=True, base=RequestContextTask) |
|
319 | def post_text_to_slack(settings, title, text, fields=None, overrides=None): | |
|
147 | def post_text_to_slack(settings, slack_data: SlackData): | |
|
148 | title = slack_data.title | |
|
149 | text = slack_data.text | |
|
150 | fields = slack_data.fields | |
|
151 | overrides = slack_data.overrides | |
|
152 | ||
|
320 | 153 | log.debug('sending %s (%s) to slack %s', title, text, settings['service']) |
|
321 | 154 | |
|
322 | 155 | fields = fields or [] |
@@ -17,7 +17,7 b'' | |||
|
17 | 17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
18 | 18 | |
|
19 | 19 | |
|
20 | ||
|
20 | import deform # noqa | |
|
21 | 21 | import deform.widget |
|
22 | 22 | import logging |
|
23 | 23 | import colander |
@@ -28,8 +28,14 b' from rhodecode.lib.colander_utils import' | |||
|
28 | 28 | from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc |
|
29 | 29 | from rhodecode.translation import _ |
|
30 | 30 | from rhodecode.integrations.types.base import ( |
|
31 | IntegrationTypeBase, get_auth, get_web_token, get_url_vars, | |
|
32 | WebhookDataHandler, WEBHOOK_URL_VARS, requests_retry_call) | |
|
31 | IntegrationTypeBase, | |
|
32 | get_auth, | |
|
33 | get_web_token, | |
|
34 | get_url_vars, | |
|
35 | WEBHOOK_URL_VARS, | |
|
36 | requests_retry_call, | |
|
37 | ) | |
|
38 | from rhodecode.integrations.types.handlers.webhook import WebhookDataHandler | |
|
33 | 39 | from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask |
|
34 | 40 | from rhodecode.model.validation_schema import widgets |
|
35 | 41 | |
@@ -57,7 +63,7 b' class WebhookSettingsSchema(colander.Sch' | |||
|
57 | 63 | widget=widgets.CodeMirrorWidget( |
|
58 | 64 | help_block_collapsable_name='Show url variables', |
|
59 | 65 | help_block_collapsable=( |
|
60 | 'E.g http://my-serv.com/trigger_job/${{event_name}}' | |
|
66 | 'E.g https://my-serv.com/trigger_job/${{event_name}}' | |
|
61 | 67 | '?PR_ID=${{pull_request_id}}' |
|
62 | 68 | '\nFull list of vars:\n{}'.format(URL_VARS)), |
|
63 | 69 | codemirror_mode='text', |
@@ -189,11 +195,11 b' class WebhookIntegrationType(Integration' | |||
|
189 | 195 | url_calls = handler(event, data) |
|
190 | 196 | log.debug('Webhook: calling following urls: %s', [x[0] for x in url_calls]) |
|
191 | 197 | |
|
192 |
run_task(post_to_webhook, |
|
|
198 | run_task(post_to_webhook, self.settings, url_calls) | |
|
193 | 199 | |
|
194 | 200 | |
|
195 | 201 | @async_task(ignore_result=True, base=RequestContextTask) |
|
196 |
def post_to_webhook( |
|
|
202 | def post_to_webhook(settings, url_calls): | |
|
197 | 203 | """ |
|
198 | 204 | Example data:: |
|
199 | 205 |
@@ -32,23 +32,27 b' def repo_push_event(backend, user_regula' | |||
|
32 | 32 | {'message': 'this is because 5b23c3532 broke stuff'}, |
|
33 | 33 | {'message': 'last commit'}, |
|
34 | 34 | ] |
|
35 |
|
|
|
36 | repo = backend.create_repo() | |
|
35 | r = backend.create_repo(commits) | |
|
36 | ||
|
37 | commit_ids = list(backend.commit_ids.values()) | |
|
38 | repo_name = backend.repo_name | |
|
39 | alias = backend.alias | |
|
40 | ||
|
37 | 41 | scm_extras = AttributeDict({ |
|
38 | 42 | 'ip': '127.0.0.1', |
|
39 | 43 | 'username': user_regular.username, |
|
40 | 44 | 'user_id': user_regular.user_id, |
|
41 | 45 | 'action': '', |
|
42 |
'repository': |
|
|
43 |
'scm': |
|
|
46 | 'repository': repo_name, | |
|
47 | 'scm': alias, | |
|
44 | 48 | 'config': '', |
|
45 | 49 | 'repo_store': '', |
|
46 |
'server_url': 'http:// |
|
|
50 | 'server_url': 'http://httpbin:9090', | |
|
47 | 51 | 'make_lock': None, |
|
48 | 52 | 'locked_by': [None], |
|
49 | 53 | 'commit_ids': commit_ids, |
|
50 | 54 | }) |
|
51 | 55 | |
|
52 |
return events.RepoPushEvent(repo_name= |
|
|
56 | return events.RepoPushEvent(repo_name=repo_name, | |
|
53 | 57 | pushed_commit_ids=commit_ids, |
|
54 | 58 | extras=scm_extras) |
@@ -1,4 +1,3 b'' | |||
|
1 | ||
|
2 | 1 |
|
|
3 | 2 | # |
|
4 | 3 | # This program is free software: you can redistribute it and/or modify |
@@ -1,4 +1,3 b'' | |||
|
1 | ||
|
2 | 1 |
|
|
3 | 2 | # |
|
4 | 3 | # This program is free software: you can redistribute it and/or modify |
@@ -18,11 +17,43 b'' | |||
|
18 | 17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 18 | |
|
20 | 19 | import pytest |
|
20 | import mock | |
|
21 | 21 | from mock import patch |
|
22 | 22 | |
|
23 | 23 | from rhodecode import events |
|
24 | from rhodecode.integrations.types.handlers.slack import SlackDataHandler | |
|
24 | 25 | from rhodecode.model.db import Session, Integration |
|
25 | 26 | from rhodecode.integrations.types.slack import SlackIntegrationType |
|
27 | from rhodecode.tests import GIT_REPO | |
|
28 | ||
|
29 | ||
|
30 | @pytest.fixture() | |
|
31 | def base_slack_data(): | |
|
32 | return { | |
|
33 | "pullrequest": { | |
|
34 | "url": "https://example.com/pr1", | |
|
35 | "pull_request_id": "1", | |
|
36 | "title": "started pr", | |
|
37 | "status": "new", | |
|
38 | "commits": ["1", "2"], | |
|
39 | "shadow_url": "http://shadow-url", | |
|
40 | }, | |
|
41 | "actor": {"username": "foo-user"}, | |
|
42 | "comment": { | |
|
43 | "comment_id": 1, | |
|
44 | "text": "test-comment", | |
|
45 | "status": "approved", | |
|
46 | "file": "text.py", | |
|
47 | "line": "1", | |
|
48 | "type": "note", | |
|
49 | }, | |
|
50 | "push": {"branches": "", "commits": []}, | |
|
51 | "repo": { | |
|
52 | "url": "https://example.com/repo1", | |
|
53 | "repo_name": GIT_REPO, | |
|
54 | "repo_type": "git", | |
|
55 | }, | |
|
56 | } | |
|
26 | 57 | |
|
27 | 58 | |
|
28 | 59 | @pytest.fixture() |
@@ -52,14 +83,66 b' def slack_integration(request, app, slac' | |||
|
52 | 83 | return integration |
|
53 | 84 | |
|
54 | 85 | |
|
86 | @pytest.fixture() | |
|
87 | def slack_integration_empty(request, app, slack_settings): | |
|
88 | slack_settings['events'] = [] | |
|
89 | integration = Integration() | |
|
90 | integration.name = 'test slack integration' | |
|
91 | integration.enabled = True | |
|
92 | integration.integration_type = SlackIntegrationType.key | |
|
93 | integration.settings = slack_settings | |
|
94 | Session().add(integration) | |
|
95 | Session().commit() | |
|
96 | request.addfinalizer(lambda: Session().delete(integration)) | |
|
97 | return integration | |
|
98 | ||
|
99 | ||
|
55 | 100 | def test_slack_push(slack_integration, repo_push_event): |
|
101 | ||
|
56 | 102 | with patch('rhodecode.integrations.types.slack.post_text_to_slack') as call: |
|
57 | 103 | events.trigger(repo_push_event) |
|
58 | assert 'pushed to' in call.call_args[0][1] | |
|
104 | ||
|
105 | assert 'pushed to' in call.call_args[0][1].title | |
|
106 | # specific commit was parsed and serialized | |
|
107 | assert 'change that fixes #41' in call.call_args[0][1].text | |
|
59 | 108 | |
|
60 | slack_integration.settings['events'] = [] | |
|
61 | Session().commit() | |
|
109 | ||
|
110 | def test_slack_push_no_events(slack_integration_empty, repo_push_event): | |
|
111 | ||
|
112 | assert Integration.get(slack_integration_empty.integration_id).settings['events'] == [] | |
|
62 | 113 | |
|
63 | 114 | with patch('rhodecode.integrations.types.slack.post_text_to_slack') as call: |
|
64 | 115 | events.trigger(repo_push_event) |
|
65 | 116 | assert not call.call_args |
|
117 | ||
|
118 | ||
|
119 | def test_slack_data_handler_wrong_event(): | |
|
120 | handler = SlackDataHandler() | |
|
121 | data = {"actor": {"username": "foo-user"}} | |
|
122 | with pytest.raises(ValueError): | |
|
123 | handler(events.RhodecodeEvent(), data) | |
|
124 | ||
|
125 | ||
|
126 | @pytest.mark.parametrize("event_type, args", [ | |
|
127 | ( | |
|
128 | events.PullRequestCommentEvent, | |
|
129 | (mock.MagicMock(name="pull-request"), mock.MagicMock(name="comment")), | |
|
130 | ), | |
|
131 | ( | |
|
132 | events.PullRequestCommentEditEvent, | |
|
133 | (mock.MagicMock(name="pull-request"), mock.MagicMock(name="comment")), | |
|
134 | ), | |
|
135 | ( | |
|
136 | events.PullRequestReviewEvent, | |
|
137 | (mock.MagicMock(name="pull-request"), mock.MagicMock(name="status")), | |
|
138 | ), | |
|
139 | ( | |
|
140 | events.RepoPushEvent, | |
|
141 | (GIT_REPO, mock.MagicMock(name="pushed_commit_ids"), mock.MagicMock(name="extras")), | |
|
142 | ), | |
|
143 | (events.PullRequestEvent, (mock.MagicMock(), )), | |
|
144 | (events.RepoCreateEvent, (mock.MagicMock(), )), | |
|
145 | ]) | |
|
146 | def test_slack_data_handler(app, event_type: events.RhodecodeEvent, args, base_slack_data): | |
|
147 | handler = SlackDataHandler() | |
|
148 | handler(event_type(*args), base_slack_data) |
@@ -18,10 +18,13 b'' | |||
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | import mock | |
|
22 | from mock import patch | |
|
21 | 23 | |
|
22 | 24 | from rhodecode import events |
|
23 | 25 | from rhodecode.lib.utils2 import AttributeDict |
|
24 | 26 | from rhodecode.integrations.types.webhook import WebhookDataHandler |
|
27 | from rhodecode.tests import GIT_REPO | |
|
25 | 28 | |
|
26 | 29 | |
|
27 | 30 | @pytest.fixture() |
@@ -38,7 +41,33 b' def base_data():' | |||
|
38 | 41 | 'actor': { |
|
39 | 42 | 'username': 'actor_name', |
|
40 | 43 | 'user_id': 1 |
|
41 | } | |
|
44 | }, | |
|
45 | "pullrequest": { | |
|
46 | "url": "https://example.com/pr1", | |
|
47 | "pull_request_id": "1", | |
|
48 | "title": "started pr", | |
|
49 | "status": "new", | |
|
50 | "commits_uid": ["1", "2"], | |
|
51 | "shadow_url": "http://shadow-url", | |
|
52 | }, | |
|
53 | "comment": { | |
|
54 | "comment_id": 1, | |
|
55 | "comment_text": "test-comment", | |
|
56 | "status": "approved", | |
|
57 | "comment_f_path": "text.py", | |
|
58 | "comment_line_no": "1", | |
|
59 | "comment_type": "note", | |
|
60 | }, | |
|
61 | "commit": { | |
|
62 | "commit_id": "efefef", | |
|
63 | "commit_branch": "master", | |
|
64 | "commit_message": "changed foo" | |
|
65 | }, | |
|
66 | "push": { | |
|
67 | "branches": "", | |
|
68 | "commits": [] | |
|
69 | }, | |
|
70 | ||
|
42 | 71 | } |
|
43 | 72 | |
|
44 | 73 | |
@@ -131,3 +160,37 b' def test_webook_parse_url_for_push_event' | |||
|
131 | 160 | urls = handler(repo_push_event, base_data) |
|
132 | 161 | assert urls == [ |
|
133 | 162 | (url, headers, base_data) for url in expected_urls] |
|
163 | ||
|
164 | ||
|
165 | @pytest.mark.parametrize("event_type, args", [ | |
|
166 | ( | |
|
167 | events.RepoPushEvent, | |
|
168 | (GIT_REPO, mock.MagicMock(name="pushed_commit_ids"), mock.MagicMock(name="extras")), | |
|
169 | ), | |
|
170 | ( | |
|
171 | events.RepoCreateEvent, | |
|
172 | (GIT_REPO,), | |
|
173 | ), | |
|
174 | ( | |
|
175 | events.RepoCommitCommentEvent, | |
|
176 | (GIT_REPO, mock.MagicMock(name="commit"), mock.MagicMock(name="comment")), | |
|
177 | ), | |
|
178 | ( | |
|
179 | events.RepoCommitCommentEditEvent, | |
|
180 | (GIT_REPO, mock.MagicMock(name="commit"), mock.MagicMock(name="comment")), | |
|
181 | ), | |
|
182 | ( | |
|
183 | events.PullRequestEvent, | |
|
184 | (mock.MagicMock(), ), | |
|
185 | ), | |
|
186 | ]) | |
|
187 | def test_webhook_data_handler(app, event_type: events.RhodecodeEvent, args, base_data): | |
|
188 | handler = WebhookDataHandler( | |
|
189 | template_url='http://server.com/${branch}/${commit_id}', | |
|
190 | headers={'exmaple-header': 'header-values'} | |
|
191 | ) | |
|
192 | handler(event_type(*args), base_data) | |
|
193 | ||
|
194 | ||
|
195 | ||
|
196 |
@@ -331,7 +331,7 b' class TestCommits(BackendTestMixin):' | |||
|
331 | 331 | assert line_no == 1 |
|
332 | 332 | assert commit_id == file_added_commit.raw_id |
|
333 | 333 | assert commit_loader() == file_added_commit |
|
334 | assert 'Foobar 3' in line | |
|
334 | assert b'Foobar 3' in line | |
|
335 | 335 | |
|
336 | 336 | def test_get_file_annotate_does_not_exist(self): |
|
337 | 337 | file_added_commit = self.repo.get_commit(commit_idx=2) |
General Comments 0
You need to be logged in to leave comments.
Login now