##// END OF EJS Templates
issue-tracker: moved example to the input box...
dan -
r4111:e62fd087 default
parent child Browse files
Show More
@@ -1,81 +1,76 b''
1 .. _rhodecode-issue-trackers-ref:
1 .. _rhodecode-issue-trackers-ref:
2
2
3 Issue Tracker Integration
3 Issue Tracker Integration
4 =========================
4 =========================
5
5
6 You can set an issue tracker connection in two ways with |RCE|.
6 You can set an issue tracker connection in two ways with |RCE|.
7
7
8 * At the instance level, you can set a default issue tracker.
8 * At the instance level, you can set a default issue tracker.
9 * At the |repo| level, you can configure an integration with a different issue
9 * At the |repo| level, you can configure an integration with a different issue
10 tracker.
10 tracker.
11
11
12 To integrate |RCE| with an issue tracker, you need to define a regular
12 To integrate |RCE| with an issue tracker, you need to define a regular
13 expression that will fetch the issue ID stored in commit messages, and replace
13 expression that will fetch the issue ID stored in commit messages, and replace
14 it with a URL. This enables |RCE| to generate a link matching each issue to the
14 it with a URL. This enables |RCE| to generate a link matching each issue to the
15 target |repo|.
15 target |repo|.
16
16
17 Default Issue Tracker Configuration
17 Default Issue Tracker Configuration
18 -----------------------------------
18 -----------------------------------
19
19
20 To integrate your issue tracker, use the following steps:
20 To integrate your issue tracker, use the following steps:
21
21
22 1. Open :menuselection:`Admin --> Settings --> Issue Tracker`.
22 1. Open :menuselection:`Admin --> Settings --> Issue Tracker`.
23 2. In the new entry field, enter the following information:
23 2. In the new entry field, enter the following information:
24
24
25 * :guilabel:`Description`: A name for this set of rules.
25 * :guilabel:`Description`: A name for this set of rules.
26 * :guilabel:`Pattern`: The regular expression that will match issues
26 * :guilabel:`Pattern`: The regular expression that will match issues
27 tagged in commit messages, or more see :ref:`issue-tr-eg-ref`.
27 tagged in commit messages, or more see :ref:`issue-tr-eg-ref`.
28 * :guilabel:`URL`: The URL to your issue tracker.
28 * :guilabel:`URL`: The URL to your issue tracker.
29 * :guilabel:`Prefix`: The prefix with which you want to mark issues.
29 * :guilabel:`Prefix`: The prefix with which you want to mark issues.
30
30
31 3. Select **Add** so save the rule to your issue tracker configuration.
31 3. Select **Add** so save the rule to your issue tracker configuration.
32
32
33 Repository Issue Tracker Configuration
33 Repository Issue Tracker Configuration
34 --------------------------------------
34 --------------------------------------
35
35
36 You can configure specific |repos| to use a different issue tracker than the
36 You can configure specific |repos| to use a different issue tracker than the
37 default one. See the instructions in :ref:`repo-it`
37 default one. See the instructions in :ref:`repo-it`
38
38
39 .. _issue-tr-eg-ref:
39 .. _issue-tr-eg-ref:
40
40
41
41 Jira Integration
42 Jira Integration
42 ----------------
43 ----------------
43
44
44 * Regex = ``(?:^#|\s#)(\w+-\d+)``
45 Please check examples in the view for configuration the issue trackers.
45 * URL = ``https://myissueserver.com/browse/${id}``
46
46 * Issue Prefix = ``#``
47
47
48 Confluence (Wiki)
48 Confluence (Wiki)
49 -----------------
49 -----------------
50
50
51 * Regex = ``(?:conf-)([A-Z0-9]+)``
51 Please check examples in the view for configuration the issue trackers.
52 * URL = ``https://example.atlassian.net/display/wiki/${id}/${repo_name}``
52
53 * issue prefix = ``CONF-``
54
53
55 Redmine Integration
54 Redmine Integration
56 -------------------
55 -------------------
57
56
58 * Regex = ``(issue-+\d+)``
57 Please check examples in the view for configuration the issue trackers.
59 * URL = ``https://myissueserver.com/redmine/issue/${id}``
58
60 * Issue Prefix = ``issue-``
61
59
62 Redmine (wiki)
60 Redmine wiki Integration
63 --------------
61 ------------------------
64
62
65 * Regex = ``(?:wiki-)([a-zA-Z0-9]+)``
63 Please check examples in the view for configuration the issue trackers.
66 * URL = ``https://example.com/redmine/projects/wiki/${repo_name}``
64
67 * Issue prefix = ``Issue-``
68
65
69 Pivotal Tracker
66 Pivotal Tracker
70 ---------------
67 ---------------
71
68
72 * Regex = ``(?:pivot-)(?<project_id>\d+)-(?<story>\d+)``
69 Please check examples in the view for configuration the issue trackers.
73 * URL = ``https://www.pivotaltracker.com/s/projects/${project_id}/stories/${story}``
70
74 * Issue prefix = ``Piv-``
75
71
76 Trello
72 Trello
77 ------
73 ------
78
74
79 * Regex = ``(?:trello-)(?<card_id>[a-zA-Z0-9]+)``
75 Please check examples in the view for configuration the issue trackers.
80 * URL = ``https://trello.com/example.com/${card_id}``
76
81 * Issue prefix = ``Trello-``
@@ -1,742 +1,742 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests import assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
34 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39 from rhodecode.apps._base import ADMIN_PREFIX
39 from rhodecode.apps._base import ADMIN_PREFIX
40
40
41 base_url = {
41 base_url = {
42
42
43 'admin_settings':
43 'admin_settings':
44 ADMIN_PREFIX +'/settings',
44 ADMIN_PREFIX +'/settings',
45 'admin_settings_update':
45 'admin_settings_update':
46 ADMIN_PREFIX + '/settings/update',
46 ADMIN_PREFIX + '/settings/update',
47 'admin_settings_global':
47 'admin_settings_global':
48 ADMIN_PREFIX + '/settings/global',
48 ADMIN_PREFIX + '/settings/global',
49 'admin_settings_global_update':
49 'admin_settings_global_update':
50 ADMIN_PREFIX + '/settings/global/update',
50 ADMIN_PREFIX + '/settings/global/update',
51 'admin_settings_vcs':
51 'admin_settings_vcs':
52 ADMIN_PREFIX + '/settings/vcs',
52 ADMIN_PREFIX + '/settings/vcs',
53 'admin_settings_vcs_update':
53 'admin_settings_vcs_update':
54 ADMIN_PREFIX + '/settings/vcs/update',
54 ADMIN_PREFIX + '/settings/vcs/update',
55 'admin_settings_vcs_svn_pattern_delete':
55 'admin_settings_vcs_svn_pattern_delete':
56 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
56 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
57 'admin_settings_mapping':
57 'admin_settings_mapping':
58 ADMIN_PREFIX + '/settings/mapping',
58 ADMIN_PREFIX + '/settings/mapping',
59 'admin_settings_mapping_update':
59 'admin_settings_mapping_update':
60 ADMIN_PREFIX + '/settings/mapping/update',
60 ADMIN_PREFIX + '/settings/mapping/update',
61 'admin_settings_visual':
61 'admin_settings_visual':
62 ADMIN_PREFIX + '/settings/visual',
62 ADMIN_PREFIX + '/settings/visual',
63 'admin_settings_visual_update':
63 'admin_settings_visual_update':
64 ADMIN_PREFIX + '/settings/visual/update',
64 ADMIN_PREFIX + '/settings/visual/update',
65 'admin_settings_issuetracker':
65 'admin_settings_issuetracker':
66 ADMIN_PREFIX + '/settings/issue-tracker',
66 ADMIN_PREFIX + '/settings/issue-tracker',
67 'admin_settings_issuetracker_update':
67 'admin_settings_issuetracker_update':
68 ADMIN_PREFIX + '/settings/issue-tracker/update',
68 ADMIN_PREFIX + '/settings/issue-tracker/update',
69 'admin_settings_issuetracker_test':
69 'admin_settings_issuetracker_test':
70 ADMIN_PREFIX + '/settings/issue-tracker/test',
70 ADMIN_PREFIX + '/settings/issue-tracker/test',
71 'admin_settings_issuetracker_delete':
71 'admin_settings_issuetracker_delete':
72 ADMIN_PREFIX + '/settings/issue-tracker/delete',
72 ADMIN_PREFIX + '/settings/issue-tracker/delete',
73 'admin_settings_email':
73 'admin_settings_email':
74 ADMIN_PREFIX + '/settings/email',
74 ADMIN_PREFIX + '/settings/email',
75 'admin_settings_email_update':
75 'admin_settings_email_update':
76 ADMIN_PREFIX + '/settings/email/update',
76 ADMIN_PREFIX + '/settings/email/update',
77 'admin_settings_hooks':
77 'admin_settings_hooks':
78 ADMIN_PREFIX + '/settings/hooks',
78 ADMIN_PREFIX + '/settings/hooks',
79 'admin_settings_hooks_update':
79 'admin_settings_hooks_update':
80 ADMIN_PREFIX + '/settings/hooks/update',
80 ADMIN_PREFIX + '/settings/hooks/update',
81 'admin_settings_hooks_delete':
81 'admin_settings_hooks_delete':
82 ADMIN_PREFIX + '/settings/hooks/delete',
82 ADMIN_PREFIX + '/settings/hooks/delete',
83 'admin_settings_search':
83 'admin_settings_search':
84 ADMIN_PREFIX + '/settings/search',
84 ADMIN_PREFIX + '/settings/search',
85 'admin_settings_labs':
85 'admin_settings_labs':
86 ADMIN_PREFIX + '/settings/labs',
86 ADMIN_PREFIX + '/settings/labs',
87 'admin_settings_labs_update':
87 'admin_settings_labs_update':
88 ADMIN_PREFIX + '/settings/labs/update',
88 ADMIN_PREFIX + '/settings/labs/update',
89
89
90 'admin_settings_sessions':
90 'admin_settings_sessions':
91 ADMIN_PREFIX + '/settings/sessions',
91 ADMIN_PREFIX + '/settings/sessions',
92 'admin_settings_sessions_cleanup':
92 'admin_settings_sessions_cleanup':
93 ADMIN_PREFIX + '/settings/sessions/cleanup',
93 ADMIN_PREFIX + '/settings/sessions/cleanup',
94 'admin_settings_system':
94 'admin_settings_system':
95 ADMIN_PREFIX + '/settings/system',
95 ADMIN_PREFIX + '/settings/system',
96 'admin_settings_system_update':
96 'admin_settings_system_update':
97 ADMIN_PREFIX + '/settings/system/updates',
97 ADMIN_PREFIX + '/settings/system/updates',
98 'admin_settings_open_source':
98 'admin_settings_open_source':
99 ADMIN_PREFIX + '/settings/open_source',
99 ADMIN_PREFIX + '/settings/open_source',
100
100
101
101
102 }[name].format(**kwargs)
102 }[name].format(**kwargs)
103
103
104 if params:
104 if params:
105 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
105 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
106 return base_url
106 return base_url
107
107
108
108
109 @pytest.mark.usefixtures('autologin_user', 'app')
109 @pytest.mark.usefixtures('autologin_user', 'app')
110 class TestAdminSettingsController(object):
110 class TestAdminSettingsController(object):
111
111
112 @pytest.mark.parametrize('urlname', [
112 @pytest.mark.parametrize('urlname', [
113 'admin_settings_vcs',
113 'admin_settings_vcs',
114 'admin_settings_mapping',
114 'admin_settings_mapping',
115 'admin_settings_global',
115 'admin_settings_global',
116 'admin_settings_visual',
116 'admin_settings_visual',
117 'admin_settings_email',
117 'admin_settings_email',
118 'admin_settings_hooks',
118 'admin_settings_hooks',
119 'admin_settings_search',
119 'admin_settings_search',
120 ])
120 ])
121 def test_simple_get(self, urlname):
121 def test_simple_get(self, urlname):
122 self.app.get(route_path(urlname))
122 self.app.get(route_path(urlname))
123
123
124 def test_create_custom_hook(self, csrf_token):
124 def test_create_custom_hook(self, csrf_token):
125 response = self.app.post(
125 response = self.app.post(
126 route_path('admin_settings_hooks_update'),
126 route_path('admin_settings_hooks_update'),
127 params={
127 params={
128 'new_hook_ui_key': 'test_hooks_1',
128 'new_hook_ui_key': 'test_hooks_1',
129 'new_hook_ui_value': 'cd /tmp',
129 'new_hook_ui_value': 'cd /tmp',
130 'csrf_token': csrf_token})
130 'csrf_token': csrf_token})
131
131
132 response = response.follow()
132 response = response.follow()
133 response.mustcontain('test_hooks_1')
133 response.mustcontain('test_hooks_1')
134 response.mustcontain('cd /tmp')
134 response.mustcontain('cd /tmp')
135
135
136 def test_create_custom_hook_delete(self, csrf_token):
136 def test_create_custom_hook_delete(self, csrf_token):
137 response = self.app.post(
137 response = self.app.post(
138 route_path('admin_settings_hooks_update'),
138 route_path('admin_settings_hooks_update'),
139 params={
139 params={
140 'new_hook_ui_key': 'test_hooks_2',
140 'new_hook_ui_key': 'test_hooks_2',
141 'new_hook_ui_value': 'cd /tmp2',
141 'new_hook_ui_value': 'cd /tmp2',
142 'csrf_token': csrf_token})
142 'csrf_token': csrf_token})
143
143
144 response = response.follow()
144 response = response.follow()
145 response.mustcontain('test_hooks_2')
145 response.mustcontain('test_hooks_2')
146 response.mustcontain('cd /tmp2')
146 response.mustcontain('cd /tmp2')
147
147
148 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
148 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
149
149
150 # delete
150 # delete
151 self.app.post(
151 self.app.post(
152 route_path('admin_settings_hooks_delete'),
152 route_path('admin_settings_hooks_delete'),
153 params={'hook_id': hook_id, 'csrf_token': csrf_token})
153 params={'hook_id': hook_id, 'csrf_token': csrf_token})
154 response = self.app.get(route_path('admin_settings_hooks'))
154 response = self.app.get(route_path('admin_settings_hooks'))
155 response.mustcontain(no=['test_hooks_2'])
155 response.mustcontain(no=['test_hooks_2'])
156 response.mustcontain(no=['cd /tmp2'])
156 response.mustcontain(no=['cd /tmp2'])
157
157
158
158
159 @pytest.mark.usefixtures('autologin_user', 'app')
159 @pytest.mark.usefixtures('autologin_user', 'app')
160 class TestAdminSettingsGlobal(object):
160 class TestAdminSettingsGlobal(object):
161
161
162 def test_pre_post_code_code_active(self, csrf_token):
162 def test_pre_post_code_code_active(self, csrf_token):
163 pre_code = 'rc-pre-code-187652122'
163 pre_code = 'rc-pre-code-187652122'
164 post_code = 'rc-postcode-98165231'
164 post_code = 'rc-postcode-98165231'
165
165
166 response = self.post_and_verify_settings({
166 response = self.post_and_verify_settings({
167 'rhodecode_pre_code': pre_code,
167 'rhodecode_pre_code': pre_code,
168 'rhodecode_post_code': post_code,
168 'rhodecode_post_code': post_code,
169 'csrf_token': csrf_token,
169 'csrf_token': csrf_token,
170 })
170 })
171
171
172 response = response.follow()
172 response = response.follow()
173 response.mustcontain(pre_code, post_code)
173 response.mustcontain(pre_code, post_code)
174
174
175 def test_pre_post_code_code_inactive(self, csrf_token):
175 def test_pre_post_code_code_inactive(self, csrf_token):
176 pre_code = 'rc-pre-code-187652122'
176 pre_code = 'rc-pre-code-187652122'
177 post_code = 'rc-postcode-98165231'
177 post_code = 'rc-postcode-98165231'
178 response = self.post_and_verify_settings({
178 response = self.post_and_verify_settings({
179 'rhodecode_pre_code': '',
179 'rhodecode_pre_code': '',
180 'rhodecode_post_code': '',
180 'rhodecode_post_code': '',
181 'csrf_token': csrf_token,
181 'csrf_token': csrf_token,
182 })
182 })
183
183
184 response = response.follow()
184 response = response.follow()
185 response.mustcontain(no=[pre_code, post_code])
185 response.mustcontain(no=[pre_code, post_code])
186
186
187 def test_captcha_activate(self, csrf_token):
187 def test_captcha_activate(self, csrf_token):
188 self.post_and_verify_settings({
188 self.post_and_verify_settings({
189 'rhodecode_captcha_private_key': '1234567890',
189 'rhodecode_captcha_private_key': '1234567890',
190 'rhodecode_captcha_public_key': '1234567890',
190 'rhodecode_captcha_public_key': '1234567890',
191 'csrf_token': csrf_token,
191 'csrf_token': csrf_token,
192 })
192 })
193
193
194 response = self.app.get(ADMIN_PREFIX + '/register')
194 response = self.app.get(ADMIN_PREFIX + '/register')
195 response.mustcontain('captcha')
195 response.mustcontain('captcha')
196
196
197 def test_captcha_deactivate(self, csrf_token):
197 def test_captcha_deactivate(self, csrf_token):
198 self.post_and_verify_settings({
198 self.post_and_verify_settings({
199 'rhodecode_captcha_private_key': '',
199 'rhodecode_captcha_private_key': '',
200 'rhodecode_captcha_public_key': '1234567890',
200 'rhodecode_captcha_public_key': '1234567890',
201 'csrf_token': csrf_token,
201 'csrf_token': csrf_token,
202 })
202 })
203
203
204 response = self.app.get(ADMIN_PREFIX + '/register')
204 response = self.app.get(ADMIN_PREFIX + '/register')
205 response.mustcontain(no=['captcha'])
205 response.mustcontain(no=['captcha'])
206
206
207 def test_title_change(self, csrf_token):
207 def test_title_change(self, csrf_token):
208 old_title = 'RhodeCode'
208 old_title = 'RhodeCode'
209
209
210 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
210 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
211 response = self.post_and_verify_settings({
211 response = self.post_and_verify_settings({
212 'rhodecode_title': new_title,
212 'rhodecode_title': new_title,
213 'csrf_token': csrf_token,
213 'csrf_token': csrf_token,
214 })
214 })
215
215
216 response = response.follow()
216 response = response.follow()
217 response.mustcontain(new_title)
217 response.mustcontain(new_title)
218
218
219 def post_and_verify_settings(self, settings):
219 def post_and_verify_settings(self, settings):
220 old_title = 'RhodeCode'
220 old_title = 'RhodeCode'
221 old_realm = 'RhodeCode authentication'
221 old_realm = 'RhodeCode authentication'
222 params = {
222 params = {
223 'rhodecode_title': old_title,
223 'rhodecode_title': old_title,
224 'rhodecode_realm': old_realm,
224 'rhodecode_realm': old_realm,
225 'rhodecode_pre_code': '',
225 'rhodecode_pre_code': '',
226 'rhodecode_post_code': '',
226 'rhodecode_post_code': '',
227 'rhodecode_captcha_private_key': '',
227 'rhodecode_captcha_private_key': '',
228 'rhodecode_captcha_public_key': '',
228 'rhodecode_captcha_public_key': '',
229 'rhodecode_create_personal_repo_group': False,
229 'rhodecode_create_personal_repo_group': False,
230 'rhodecode_personal_repo_group_pattern': '${username}',
230 'rhodecode_personal_repo_group_pattern': '${username}',
231 }
231 }
232 params.update(settings)
232 params.update(settings)
233 response = self.app.post(
233 response = self.app.post(
234 route_path('admin_settings_global_update'), params=params)
234 route_path('admin_settings_global_update'), params=params)
235
235
236 assert_session_flash(response, 'Updated application settings')
236 assert_session_flash(response, 'Updated application settings')
237 app_settings = SettingsModel().get_all_settings()
237 app_settings = SettingsModel().get_all_settings()
238 del settings['csrf_token']
238 del settings['csrf_token']
239 for key, value in settings.iteritems():
239 for key, value in settings.iteritems():
240 assert app_settings[key] == value.decode('utf-8')
240 assert app_settings[key] == value.decode('utf-8')
241
241
242 return response
242 return response
243
243
244
244
245 @pytest.mark.usefixtures('autologin_user', 'app')
245 @pytest.mark.usefixtures('autologin_user', 'app')
246 class TestAdminSettingsVcs(object):
246 class TestAdminSettingsVcs(object):
247
247
248 def test_contains_svn_default_patterns(self):
248 def test_contains_svn_default_patterns(self):
249 response = self.app.get(route_path('admin_settings_vcs'))
249 response = self.app.get(route_path('admin_settings_vcs'))
250 expected_patterns = [
250 expected_patterns = [
251 '/trunk',
251 '/trunk',
252 '/branches/*',
252 '/branches/*',
253 '/tags/*',
253 '/tags/*',
254 ]
254 ]
255 for pattern in expected_patterns:
255 for pattern in expected_patterns:
256 response.mustcontain(pattern)
256 response.mustcontain(pattern)
257
257
258 def test_add_new_svn_branch_and_tag_pattern(
258 def test_add_new_svn_branch_and_tag_pattern(
259 self, backend_svn, form_defaults, disable_sql_cache,
259 self, backend_svn, form_defaults, disable_sql_cache,
260 csrf_token):
260 csrf_token):
261 form_defaults.update({
261 form_defaults.update({
262 'new_svn_branch': '/exp/branches/*',
262 'new_svn_branch': '/exp/branches/*',
263 'new_svn_tag': '/important_tags/*',
263 'new_svn_tag': '/important_tags/*',
264 'csrf_token': csrf_token,
264 'csrf_token': csrf_token,
265 })
265 })
266
266
267 response = self.app.post(
267 response = self.app.post(
268 route_path('admin_settings_vcs_update'),
268 route_path('admin_settings_vcs_update'),
269 params=form_defaults, status=302)
269 params=form_defaults, status=302)
270 response = response.follow()
270 response = response.follow()
271
271
272 # Expect to find the new values on the page
272 # Expect to find the new values on the page
273 response.mustcontain('/exp/branches/*')
273 response.mustcontain('/exp/branches/*')
274 response.mustcontain('/important_tags/*')
274 response.mustcontain('/important_tags/*')
275
275
276 # Expect that those patterns are used to match branches and tags now
276 # Expect that those patterns are used to match branches and tags now
277 repo = backend_svn['svn-simple-layout'].scm_instance()
277 repo = backend_svn['svn-simple-layout'].scm_instance()
278 assert 'exp/branches/exp-sphinx-docs' in repo.branches
278 assert 'exp/branches/exp-sphinx-docs' in repo.branches
279 assert 'important_tags/v0.5' in repo.tags
279 assert 'important_tags/v0.5' in repo.tags
280
280
281 def test_add_same_svn_value_twice_shows_an_error_message(
281 def test_add_same_svn_value_twice_shows_an_error_message(
282 self, form_defaults, csrf_token, settings_util):
282 self, form_defaults, csrf_token, settings_util):
283 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
283 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
284 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
284 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
285
285
286 response = self.app.post(
286 response = self.app.post(
287 route_path('admin_settings_vcs_update'),
287 route_path('admin_settings_vcs_update'),
288 params={
288 params={
289 'paths_root_path': form_defaults['paths_root_path'],
289 'paths_root_path': form_defaults['paths_root_path'],
290 'new_svn_branch': '/test',
290 'new_svn_branch': '/test',
291 'new_svn_tag': '/test',
291 'new_svn_tag': '/test',
292 'csrf_token': csrf_token,
292 'csrf_token': csrf_token,
293 },
293 },
294 status=200)
294 status=200)
295
295
296 response.mustcontain("Pattern already exists")
296 response.mustcontain("Pattern already exists")
297 response.mustcontain("Some form inputs contain invalid data.")
297 response.mustcontain("Some form inputs contain invalid data.")
298
298
299 @pytest.mark.parametrize('section', [
299 @pytest.mark.parametrize('section', [
300 'vcs_svn_branch',
300 'vcs_svn_branch',
301 'vcs_svn_tag',
301 'vcs_svn_tag',
302 ])
302 ])
303 def test_delete_svn_patterns(
303 def test_delete_svn_patterns(
304 self, section, csrf_token, settings_util):
304 self, section, csrf_token, settings_util):
305 setting = settings_util.create_rhodecode_ui(
305 setting = settings_util.create_rhodecode_ui(
306 section, '/test_delete', cleanup=False)
306 section, '/test_delete', cleanup=False)
307
307
308 self.app.post(
308 self.app.post(
309 route_path('admin_settings_vcs_svn_pattern_delete'),
309 route_path('admin_settings_vcs_svn_pattern_delete'),
310 params={
310 params={
311 'delete_svn_pattern': setting.ui_id,
311 'delete_svn_pattern': setting.ui_id,
312 'csrf_token': csrf_token},
312 'csrf_token': csrf_token},
313 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
313 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
314
314
315 @pytest.mark.parametrize('section', [
315 @pytest.mark.parametrize('section', [
316 'vcs_svn_branch',
316 'vcs_svn_branch',
317 'vcs_svn_tag',
317 'vcs_svn_tag',
318 ])
318 ])
319 def test_delete_svn_patterns_raises_404_when_no_xhr(
319 def test_delete_svn_patterns_raises_404_when_no_xhr(
320 self, section, csrf_token, settings_util):
320 self, section, csrf_token, settings_util):
321 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
321 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
322
322
323 self.app.post(
323 self.app.post(
324 route_path('admin_settings_vcs_svn_pattern_delete'),
324 route_path('admin_settings_vcs_svn_pattern_delete'),
325 params={
325 params={
326 'delete_svn_pattern': setting.ui_id,
326 'delete_svn_pattern': setting.ui_id,
327 'csrf_token': csrf_token},
327 'csrf_token': csrf_token},
328 status=404)
328 status=404)
329
329
330 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
330 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
331 form_defaults.update({
331 form_defaults.update({
332 'csrf_token': csrf_token,
332 'csrf_token': csrf_token,
333 'extensions_hgsubversion': 'True',
333 'extensions_hgsubversion': 'True',
334 })
334 })
335 response = self.app.post(
335 response = self.app.post(
336 route_path('admin_settings_vcs_update'),
336 route_path('admin_settings_vcs_update'),
337 params=form_defaults,
337 params=form_defaults,
338 status=302)
338 status=302)
339
339
340 response = response.follow()
340 response = response.follow()
341 extensions_input = (
341 extensions_input = (
342 '<input id="extensions_hgsubversion" '
342 '<input id="extensions_hgsubversion" '
343 'name="extensions_hgsubversion" type="checkbox" '
343 'name="extensions_hgsubversion" type="checkbox" '
344 'value="True" checked="checked" />')
344 'value="True" checked="checked" />')
345 response.mustcontain(extensions_input)
345 response.mustcontain(extensions_input)
346
346
347 def test_extensions_hgevolve(self, form_defaults, csrf_token):
347 def test_extensions_hgevolve(self, form_defaults, csrf_token):
348 form_defaults.update({
348 form_defaults.update({
349 'csrf_token': csrf_token,
349 'csrf_token': csrf_token,
350 'extensions_evolve': 'True',
350 'extensions_evolve': 'True',
351 })
351 })
352 response = self.app.post(
352 response = self.app.post(
353 route_path('admin_settings_vcs_update'),
353 route_path('admin_settings_vcs_update'),
354 params=form_defaults,
354 params=form_defaults,
355 status=302)
355 status=302)
356
356
357 response = response.follow()
357 response = response.follow()
358 extensions_input = (
358 extensions_input = (
359 '<input id="extensions_evolve" '
359 '<input id="extensions_evolve" '
360 'name="extensions_evolve" type="checkbox" '
360 'name="extensions_evolve" type="checkbox" '
361 'value="True" checked="checked" />')
361 'value="True" checked="checked" />')
362 response.mustcontain(extensions_input)
362 response.mustcontain(extensions_input)
363
363
364 def test_has_a_section_for_pull_request_settings(self):
364 def test_has_a_section_for_pull_request_settings(self):
365 response = self.app.get(route_path('admin_settings_vcs'))
365 response = self.app.get(route_path('admin_settings_vcs'))
366 response.mustcontain('Pull Request Settings')
366 response.mustcontain('Pull Request Settings')
367
367
368 def test_has_an_input_for_invalidation_of_inline_comments(self):
368 def test_has_an_input_for_invalidation_of_inline_comments(self):
369 response = self.app.get(route_path('admin_settings_vcs'))
369 response = self.app.get(route_path('admin_settings_vcs'))
370 assert_response = response.assert_response()
370 assert_response = response.assert_response()
371 assert_response.one_element_exists(
371 assert_response.one_element_exists(
372 '[name=rhodecode_use_outdated_comments]')
372 '[name=rhodecode_use_outdated_comments]')
373
373
374 @pytest.mark.parametrize('new_value', [True, False])
374 @pytest.mark.parametrize('new_value', [True, False])
375 def test_allows_to_change_invalidation_of_inline_comments(
375 def test_allows_to_change_invalidation_of_inline_comments(
376 self, form_defaults, csrf_token, new_value):
376 self, form_defaults, csrf_token, new_value):
377 setting_key = 'use_outdated_comments'
377 setting_key = 'use_outdated_comments'
378 setting = SettingsModel().create_or_update_setting(
378 setting = SettingsModel().create_or_update_setting(
379 setting_key, not new_value, 'bool')
379 setting_key, not new_value, 'bool')
380 Session().add(setting)
380 Session().add(setting)
381 Session().commit()
381 Session().commit()
382
382
383 form_defaults.update({
383 form_defaults.update({
384 'csrf_token': csrf_token,
384 'csrf_token': csrf_token,
385 'rhodecode_use_outdated_comments': str(new_value),
385 'rhodecode_use_outdated_comments': str(new_value),
386 })
386 })
387 response = self.app.post(
387 response = self.app.post(
388 route_path('admin_settings_vcs_update'),
388 route_path('admin_settings_vcs_update'),
389 params=form_defaults,
389 params=form_defaults,
390 status=302)
390 status=302)
391 response = response.follow()
391 response = response.follow()
392 setting = SettingsModel().get_setting_by_name(setting_key)
392 setting = SettingsModel().get_setting_by_name(setting_key)
393 assert setting.app_settings_value is new_value
393 assert setting.app_settings_value is new_value
394
394
395 @pytest.mark.parametrize('new_value', [True, False])
395 @pytest.mark.parametrize('new_value', [True, False])
396 def test_allows_to_change_hg_rebase_merge_strategy(
396 def test_allows_to_change_hg_rebase_merge_strategy(
397 self, form_defaults, csrf_token, new_value):
397 self, form_defaults, csrf_token, new_value):
398 setting_key = 'hg_use_rebase_for_merging'
398 setting_key = 'hg_use_rebase_for_merging'
399
399
400 form_defaults.update({
400 form_defaults.update({
401 'csrf_token': csrf_token,
401 'csrf_token': csrf_token,
402 'rhodecode_' + setting_key: str(new_value),
402 'rhodecode_' + setting_key: str(new_value),
403 })
403 })
404
404
405 with mock.patch.dict(
405 with mock.patch.dict(
406 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
406 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
407 self.app.post(
407 self.app.post(
408 route_path('admin_settings_vcs_update'),
408 route_path('admin_settings_vcs_update'),
409 params=form_defaults,
409 params=form_defaults,
410 status=302)
410 status=302)
411
411
412 setting = SettingsModel().get_setting_by_name(setting_key)
412 setting = SettingsModel().get_setting_by_name(setting_key)
413 assert setting.app_settings_value is new_value
413 assert setting.app_settings_value is new_value
414
414
415 @pytest.fixture()
415 @pytest.fixture()
416 def disable_sql_cache(self, request):
416 def disable_sql_cache(self, request):
417 patcher = mock.patch(
417 patcher = mock.patch(
418 'rhodecode.lib.caching_query.FromCache.process_query')
418 'rhodecode.lib.caching_query.FromCache.process_query')
419 request.addfinalizer(patcher.stop)
419 request.addfinalizer(patcher.stop)
420 patcher.start()
420 patcher.start()
421
421
422 @pytest.fixture()
422 @pytest.fixture()
423 def form_defaults(self):
423 def form_defaults(self):
424 from rhodecode.apps.admin.views.settings import AdminSettingsView
424 from rhodecode.apps.admin.views.settings import AdminSettingsView
425 return AdminSettingsView._form_defaults()
425 return AdminSettingsView._form_defaults()
426
426
427 # TODO: johbo: What we really want is to checkpoint before a test run and
427 # TODO: johbo: What we really want is to checkpoint before a test run and
428 # reset the session afterwards.
428 # reset the session afterwards.
429 @pytest.fixture(scope='class', autouse=True)
429 @pytest.fixture(scope='class', autouse=True)
430 def cleanup_settings(self, request, baseapp):
430 def cleanup_settings(self, request, baseapp):
431 ui_id = RhodeCodeUi.ui_id
431 ui_id = RhodeCodeUi.ui_id
432 original_ids = list(
432 original_ids = list(
433 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
433 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
434
434
435 @request.addfinalizer
435 @request.addfinalizer
436 def cleanup():
436 def cleanup():
437 RhodeCodeUi.query().filter(
437 RhodeCodeUi.query().filter(
438 ui_id.notin_(original_ids)).delete(False)
438 ui_id.notin_(original_ids)).delete(False)
439
439
440
440
441 @pytest.mark.usefixtures('autologin_user', 'app')
441 @pytest.mark.usefixtures('autologin_user', 'app')
442 class TestLabsSettings(object):
442 class TestLabsSettings(object):
443 def test_get_settings_page_disabled(self):
443 def test_get_settings_page_disabled(self):
444 with mock.patch.dict(
444 with mock.patch.dict(
445 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
445 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
446
446
447 response = self.app.get(
447 response = self.app.get(
448 route_path('admin_settings_labs'), status=302)
448 route_path('admin_settings_labs'), status=302)
449
449
450 assert response.location.endswith(route_path('admin_settings'))
450 assert response.location.endswith(route_path('admin_settings'))
451
451
452 def test_get_settings_page_enabled(self):
452 def test_get_settings_page_enabled(self):
453 from rhodecode.apps.admin.views import settings
453 from rhodecode.apps.admin.views import settings
454 lab_settings = [
454 lab_settings = [
455 settings.LabSetting(
455 settings.LabSetting(
456 key='rhodecode_bool',
456 key='rhodecode_bool',
457 type='bool',
457 type='bool',
458 group='bool group',
458 group='bool group',
459 label='bool label',
459 label='bool label',
460 help='bool help'
460 help='bool help'
461 ),
461 ),
462 settings.LabSetting(
462 settings.LabSetting(
463 key='rhodecode_text',
463 key='rhodecode_text',
464 type='unicode',
464 type='unicode',
465 group='text group',
465 group='text group',
466 label='text label',
466 label='text label',
467 help='text help'
467 help='text help'
468 ),
468 ),
469 ]
469 ]
470 with mock.patch.dict(rhodecode.CONFIG,
470 with mock.patch.dict(rhodecode.CONFIG,
471 {'labs_settings_active': 'true'}):
471 {'labs_settings_active': 'true'}):
472 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
472 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
473 response = self.app.get(route_path('admin_settings_labs'))
473 response = self.app.get(route_path('admin_settings_labs'))
474
474
475 assert '<label>bool group:</label>' in response
475 assert '<label>bool group:</label>' in response
476 assert '<label for="rhodecode_bool">bool label</label>' in response
476 assert '<label for="rhodecode_bool">bool label</label>' in response
477 assert '<p class="help-block">bool help</p>' in response
477 assert '<p class="help-block">bool help</p>' in response
478 assert 'name="rhodecode_bool" type="checkbox"' in response
478 assert 'name="rhodecode_bool" type="checkbox"' in response
479
479
480 assert '<label>text group:</label>' in response
480 assert '<label>text group:</label>' in response
481 assert '<label for="rhodecode_text">text label</label>' in response
481 assert '<label for="rhodecode_text">text label</label>' in response
482 assert '<p class="help-block">text help</p>' in response
482 assert '<p class="help-block">text help</p>' in response
483 assert 'name="rhodecode_text" size="60" type="text"' in response
483 assert 'name="rhodecode_text" size="60" type="text"' in response
484
484
485
485
486 @pytest.mark.usefixtures('app')
486 @pytest.mark.usefixtures('app')
487 class TestOpenSourceLicenses(object):
487 class TestOpenSourceLicenses(object):
488
488
489 def test_records_are_displayed(self, autologin_user):
489 def test_records_are_displayed(self, autologin_user):
490 sample_licenses = [
490 sample_licenses = [
491 {
491 {
492 "license": [
492 "license": [
493 {
493 {
494 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
494 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
495 "shortName": "bsdOriginal",
495 "shortName": "bsdOriginal",
496 "spdxId": "BSD-4-Clause",
496 "spdxId": "BSD-4-Clause",
497 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
497 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
498 }
498 }
499 ],
499 ],
500 "name": "python2.7-coverage-3.7.1"
500 "name": "python2.7-coverage-3.7.1"
501 },
501 },
502 {
502 {
503 "license": [
503 "license": [
504 {
504 {
505 "fullName": "MIT License",
505 "fullName": "MIT License",
506 "shortName": "mit",
506 "shortName": "mit",
507 "spdxId": "MIT",
507 "spdxId": "MIT",
508 "url": "http://spdx.org/licenses/MIT.html"
508 "url": "http://spdx.org/licenses/MIT.html"
509 }
509 }
510 ],
510 ],
511 "name": "python2.7-bootstrapped-pip-9.0.1"
511 "name": "python2.7-bootstrapped-pip-9.0.1"
512 },
512 },
513 ]
513 ]
514 read_licenses_patch = mock.patch(
514 read_licenses_patch = mock.patch(
515 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
515 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
516 return_value=sample_licenses)
516 return_value=sample_licenses)
517 with read_licenses_patch:
517 with read_licenses_patch:
518 response = self.app.get(
518 response = self.app.get(
519 route_path('admin_settings_open_source'), status=200)
519 route_path('admin_settings_open_source'), status=200)
520
520
521 assert_response = response.assert_response()
521 assert_response = response.assert_response()
522 assert_response.element_contains(
522 assert_response.element_contains(
523 '.panel-heading', 'Licenses of Third Party Packages')
523 '.panel-heading', 'Licenses of Third Party Packages')
524 for license_data in sample_licenses:
524 for license_data in sample_licenses:
525 response.mustcontain(license_data["license"][0]["spdxId"])
525 response.mustcontain(license_data["license"][0]["spdxId"])
526 assert_response.element_contains('.panel-body', license_data["name"])
526 assert_response.element_contains('.panel-body', license_data["name"])
527
527
528 def test_records_can_be_read(self, autologin_user):
528 def test_records_can_be_read(self, autologin_user):
529 response = self.app.get(
529 response = self.app.get(
530 route_path('admin_settings_open_source'), status=200)
530 route_path('admin_settings_open_source'), status=200)
531 assert_response = response.assert_response()
531 assert_response = response.assert_response()
532 assert_response.element_contains(
532 assert_response.element_contains(
533 '.panel-heading', 'Licenses of Third Party Packages')
533 '.panel-heading', 'Licenses of Third Party Packages')
534
534
535 def test_forbidden_when_normal_user(self, autologin_regular_user):
535 def test_forbidden_when_normal_user(self, autologin_regular_user):
536 self.app.get(
536 self.app.get(
537 route_path('admin_settings_open_source'), status=404)
537 route_path('admin_settings_open_source'), status=404)
538
538
539
539
540 @pytest.mark.usefixtures('app')
540 @pytest.mark.usefixtures('app')
541 class TestUserSessions(object):
541 class TestUserSessions(object):
542
542
543 def test_forbidden_when_normal_user(self, autologin_regular_user):
543 def test_forbidden_when_normal_user(self, autologin_regular_user):
544 self.app.get(route_path('admin_settings_sessions'), status=404)
544 self.app.get(route_path('admin_settings_sessions'), status=404)
545
545
546 def test_show_sessions_page(self, autologin_user):
546 def test_show_sessions_page(self, autologin_user):
547 response = self.app.get(route_path('admin_settings_sessions'), status=200)
547 response = self.app.get(route_path('admin_settings_sessions'), status=200)
548 response.mustcontain('file')
548 response.mustcontain('file')
549
549
550 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
550 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
551
551
552 post_data = {
552 post_data = {
553 'csrf_token': csrf_token,
553 'csrf_token': csrf_token,
554 'expire_days': '60'
554 'expire_days': '60'
555 }
555 }
556 response = self.app.post(
556 response = self.app.post(
557 route_path('admin_settings_sessions_cleanup'), params=post_data,
557 route_path('admin_settings_sessions_cleanup'), params=post_data,
558 status=302)
558 status=302)
559 assert_session_flash(response, 'Cleaned up old sessions')
559 assert_session_flash(response, 'Cleaned up old sessions')
560
560
561
561
562 @pytest.mark.usefixtures('app')
562 @pytest.mark.usefixtures('app')
563 class TestAdminSystemInfo(object):
563 class TestAdminSystemInfo(object):
564
564
565 def test_forbidden_when_normal_user(self, autologin_regular_user):
565 def test_forbidden_when_normal_user(self, autologin_regular_user):
566 self.app.get(route_path('admin_settings_system'), status=404)
566 self.app.get(route_path('admin_settings_system'), status=404)
567
567
568 def test_system_info_page(self, autologin_user):
568 def test_system_info_page(self, autologin_user):
569 response = self.app.get(route_path('admin_settings_system'))
569 response = self.app.get(route_path('admin_settings_system'))
570 response.mustcontain('RhodeCode Community Edition, version {}'.format(
570 response.mustcontain('RhodeCode Community Edition, version {}'.format(
571 rhodecode.__version__))
571 rhodecode.__version__))
572
572
573 def test_system_update_new_version(self, autologin_user):
573 def test_system_update_new_version(self, autologin_user):
574 update_data = {
574 update_data = {
575 'versions': [
575 'versions': [
576 {
576 {
577 'version': '100.3.1415926535',
577 'version': '100.3.1415926535',
578 'general': 'The latest version we are ever going to ship'
578 'general': 'The latest version we are ever going to ship'
579 },
579 },
580 {
580 {
581 'version': '0.0.0',
581 'version': '0.0.0',
582 'general': 'The first version we ever shipped'
582 'general': 'The first version we ever shipped'
583 }
583 }
584 ]
584 ]
585 }
585 }
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
587 response = self.app.get(route_path('admin_settings_system_update'))
587 response = self.app.get(route_path('admin_settings_system_update'))
588 response.mustcontain('A <b>new version</b> is available')
588 response.mustcontain('A <b>new version</b> is available')
589
589
590 def test_system_update_nothing_new(self, autologin_user):
590 def test_system_update_nothing_new(self, autologin_user):
591 update_data = {
591 update_data = {
592 'versions': [
592 'versions': [
593 {
593 {
594 'version': '0.0.0',
594 'version': '0.0.0',
595 'general': 'The first version we ever shipped'
595 'general': 'The first version we ever shipped'
596 }
596 }
597 ]
597 ]
598 }
598 }
599 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
599 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
600 response = self.app.get(route_path('admin_settings_system_update'))
600 response = self.app.get(route_path('admin_settings_system_update'))
601 response.mustcontain(
601 response.mustcontain(
602 'This instance is already running the <b>latest</b> stable version')
602 'This instance is already running the <b>latest</b> stable version')
603
603
604 def test_system_update_bad_response(self, autologin_user):
604 def test_system_update_bad_response(self, autologin_user):
605 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
605 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
606 response = self.app.get(route_path('admin_settings_system_update'))
606 response = self.app.get(route_path('admin_settings_system_update'))
607 response.mustcontain(
607 response.mustcontain(
608 'Bad data sent from update server')
608 'Bad data sent from update server')
609
609
610
610
611 @pytest.mark.usefixtures("app")
611 @pytest.mark.usefixtures("app")
612 class TestAdminSettingsIssueTracker(object):
612 class TestAdminSettingsIssueTracker(object):
613 RC_PREFIX = 'rhodecode_'
613 RC_PREFIX = 'rhodecode_'
614 SHORT_PATTERN_KEY = 'issuetracker_pat_'
614 SHORT_PATTERN_KEY = 'issuetracker_pat_'
615 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
615 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
616
616
617 def test_issuetracker_index(self, autologin_user):
617 def test_issuetracker_index(self, autologin_user):
618 response = self.app.get(route_path('admin_settings_issuetracker'))
618 response = self.app.get(route_path('admin_settings_issuetracker'))
619 assert response.status_code == 200
619 assert response.status_code == 200
620
620
621 def test_add_empty_issuetracker_pattern(
621 def test_add_empty_issuetracker_pattern(
622 self, request, autologin_user, csrf_token):
622 self, request, autologin_user, csrf_token):
623 post_url = route_path('admin_settings_issuetracker_update')
623 post_url = route_path('admin_settings_issuetracker_update')
624 post_data = {
624 post_data = {
625 'csrf_token': csrf_token
625 'csrf_token': csrf_token
626 }
626 }
627 self.app.post(post_url, post_data, status=302)
627 self.app.post(post_url, post_data, status=302)
628
628
629 def test_add_issuetracker_pattern(
629 def test_add_issuetracker_pattern(
630 self, request, autologin_user, csrf_token):
630 self, request, autologin_user, csrf_token):
631 pattern = 'issuetracker_pat'
631 pattern = 'issuetracker_pat'
632 another_pattern = pattern+'1'
632 another_pattern = pattern+'1'
633 post_url = route_path('admin_settings_issuetracker_update')
633 post_url = route_path('admin_settings_issuetracker_update')
634 post_data = {
634 post_data = {
635 'new_pattern_pattern_0': pattern,
635 'new_pattern_pattern_0': pattern,
636 'new_pattern_url_0': 'http://url',
636 'new_pattern_url_0': 'http://url',
637 'new_pattern_prefix_0': 'prefix',
637 'new_pattern_prefix_0': 'prefix',
638 'new_pattern_description_0': 'description',
638 'new_pattern_description_0': 'description',
639 'new_pattern_pattern_1': another_pattern,
639 'new_pattern_pattern_1': another_pattern,
640 'new_pattern_url_1': 'https://url1',
640 'new_pattern_url_1': 'https://url1',
641 'new_pattern_prefix_1': 'prefix1',
641 'new_pattern_prefix_1': 'prefix1',
642 'new_pattern_description_1': 'description1',
642 'new_pattern_description_1': 'description1',
643 'csrf_token': csrf_token
643 'csrf_token': csrf_token
644 }
644 }
645 self.app.post(post_url, post_data, status=302)
645 self.app.post(post_url, post_data, status=302)
646 settings = SettingsModel().get_all_settings()
646 settings = SettingsModel().get_all_settings()
647 self.uid = md5(pattern)
647 self.uid = md5(pattern)
648 assert settings[self.PATTERN_KEY+self.uid] == pattern
648 assert settings[self.PATTERN_KEY+self.uid] == pattern
649 self.another_uid = md5(another_pattern)
649 self.another_uid = md5(another_pattern)
650 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
650 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
651
651
652 @request.addfinalizer
652 @request.addfinalizer
653 def cleanup():
653 def cleanup():
654 defaults = SettingsModel().get_all_settings()
654 defaults = SettingsModel().get_all_settings()
655
655
656 entries = [name for name in defaults if (
656 entries = [name for name in defaults if (
657 (self.uid in name) or (self.another_uid) in name)]
657 (self.uid in name) or (self.another_uid) in name)]
658 start = len(self.RC_PREFIX)
658 start = len(self.RC_PREFIX)
659 for del_key in entries:
659 for del_key in entries:
660 # TODO: anderson: get_by_name needs name without prefix
660 # TODO: anderson: get_by_name needs name without prefix
661 entry = SettingsModel().get_setting_by_name(del_key[start:])
661 entry = SettingsModel().get_setting_by_name(del_key[start:])
662 Session().delete(entry)
662 Session().delete(entry)
663
663
664 Session().commit()
664 Session().commit()
665
665
666 def test_edit_issuetracker_pattern(
666 def test_edit_issuetracker_pattern(
667 self, autologin_user, backend, csrf_token, request):
667 self, autologin_user, backend, csrf_token, request):
668 old_pattern = 'issuetracker_pat'
668 old_pattern = 'issuetracker_pat'
669 old_uid = md5(old_pattern)
669 old_uid = md5(old_pattern)
670 pattern = 'issuetracker_pat_new'
670 pattern = 'issuetracker_pat_new'
671 self.new_uid = md5(pattern)
671 self.new_uid = md5(pattern)
672
672
673 SettingsModel().create_or_update_setting(
673 SettingsModel().create_or_update_setting(
674 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
674 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
675
675
676 post_url = route_path('admin_settings_issuetracker_update')
676 post_url = route_path('admin_settings_issuetracker_update')
677 post_data = {
677 post_data = {
678 'new_pattern_pattern_0': pattern,
678 'new_pattern_pattern_0': pattern,
679 'new_pattern_url_0': 'https://url',
679 'new_pattern_url_0': 'https://url',
680 'new_pattern_prefix_0': 'prefix',
680 'new_pattern_prefix_0': 'prefix',
681 'new_pattern_description_0': 'description',
681 'new_pattern_description_0': 'description',
682 'uid': old_uid,
682 'uid': old_uid,
683 'csrf_token': csrf_token
683 'csrf_token': csrf_token
684 }
684 }
685 self.app.post(post_url, post_data, status=302)
685 self.app.post(post_url, post_data, status=302)
686 settings = SettingsModel().get_all_settings()
686 settings = SettingsModel().get_all_settings()
687 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
687 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
688 assert self.PATTERN_KEY+old_uid not in settings
688 assert self.PATTERN_KEY+old_uid not in settings
689
689
690 @request.addfinalizer
690 @request.addfinalizer
691 def cleanup():
691 def cleanup():
692 IssueTrackerSettingsModel().delete_entries(self.new_uid)
692 IssueTrackerSettingsModel().delete_entries(self.new_uid)
693
693
694 def test_replace_issuetracker_pattern_description(
694 def test_replace_issuetracker_pattern_description(
695 self, autologin_user, csrf_token, request, settings_util):
695 self, autologin_user, csrf_token, request, settings_util):
696 prefix = 'issuetracker'
696 prefix = 'issuetracker'
697 pattern = 'issuetracker_pat'
697 pattern = 'issuetracker_pat'
698 self.uid = md5(pattern)
698 self.uid = md5(pattern)
699 pattern_key = '_'.join([prefix, 'pat', self.uid])
699 pattern_key = '_'.join([prefix, 'pat', self.uid])
700 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
700 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
701 desc_key = '_'.join([prefix, 'desc', self.uid])
701 desc_key = '_'.join([prefix, 'desc', self.uid])
702 rc_desc_key = '_'.join(['rhodecode', desc_key])
702 rc_desc_key = '_'.join(['rhodecode', desc_key])
703 new_description = 'new_description'
703 new_description = 'new_description'
704
704
705 settings_util.create_rhodecode_setting(
705 settings_util.create_rhodecode_setting(
706 pattern_key, pattern, 'unicode', cleanup=False)
706 pattern_key, pattern, 'unicode', cleanup=False)
707 settings_util.create_rhodecode_setting(
707 settings_util.create_rhodecode_setting(
708 desc_key, 'old description', 'unicode', cleanup=False)
708 desc_key, 'old description', 'unicode', cleanup=False)
709
709
710 post_url = route_path('admin_settings_issuetracker_update')
710 post_url = route_path('admin_settings_issuetracker_update')
711 post_data = {
711 post_data = {
712 'new_pattern_pattern_0': pattern,
712 'new_pattern_pattern_0': pattern,
713 'new_pattern_url_0': 'https://url',
713 'new_pattern_url_0': 'https://url',
714 'new_pattern_prefix_0': 'prefix',
714 'new_pattern_prefix_0': 'prefix',
715 'new_pattern_description_0': new_description,
715 'new_pattern_description_0': new_description,
716 'uid': self.uid,
716 'uid': self.uid,
717 'csrf_token': csrf_token
717 'csrf_token': csrf_token
718 }
718 }
719 self.app.post(post_url, post_data, status=302)
719 self.app.post(post_url, post_data, status=302)
720 settings = SettingsModel().get_all_settings()
720 settings = SettingsModel().get_all_settings()
721 assert settings[rc_pattern_key] == pattern
721 assert settings[rc_pattern_key] == pattern
722 assert settings[rc_desc_key] == new_description
722 assert settings[rc_desc_key] == new_description
723
723
724 @request.addfinalizer
724 @request.addfinalizer
725 def cleanup():
725 def cleanup():
726 IssueTrackerSettingsModel().delete_entries(self.uid)
726 IssueTrackerSettingsModel().delete_entries(self.uid)
727
727
728 def test_delete_issuetracker_pattern(
728 def test_delete_issuetracker_pattern(
729 self, autologin_user, backend, csrf_token, settings_util):
729 self, autologin_user, backend, csrf_token, settings_util, xhr_header):
730 pattern = 'issuetracker_pat'
730 pattern = 'issuetracker_pat'
731 uid = md5(pattern)
731 uid = md5(pattern)
732 settings_util.create_rhodecode_setting(
732 settings_util.create_rhodecode_setting(
733 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
733 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
734
734
735 post_url = route_path('admin_settings_issuetracker_delete')
735 post_url = route_path('admin_settings_issuetracker_delete')
736 post_data = {
736 post_data = {
737 'uid': uid,
737 'uid': uid,
738 'csrf_token': csrf_token
738 'csrf_token': csrf_token
739 }
739 }
740 self.app.post(post_url, post_data, status=302)
740 self.app.post(post_url, post_data, extra_environ=xhr_header, status=200)
741 settings = SettingsModel().get_all_settings()
741 settings = SettingsModel().get_all_settings()
742 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
742 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,780 +1,783 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import formencode.htmlfill
27 import formencode.htmlfill
28
28
29 import rhodecode
29 import rhodecode
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 from rhodecode.apps._base import BaseAppView
35 from rhodecode.apps._base import BaseAppView
36 from rhodecode.apps._base.navigation import navigation_list
36 from rhodecode.apps._base.navigation import navigation_list
37 from rhodecode.apps.svn_support.config_keys import generate_config
37 from rhodecode.apps.svn_support.config_keys import generate_config
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils import repo2db_mapper
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 from rhodecode.lib.index import searcher_from_config
44 from rhodecode.lib.index import searcher_from_config
45
45
46 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.db import RhodeCodeUi, Repository
47 from rhodecode.model.forms import (ApplicationSettingsForm,
47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 LabsSettingsForm, IssueTrackerPatternsForm)
49 LabsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70
70
71 return c
71 return c
72
72
73 @classmethod
73 @classmethod
74 def _get_ui_settings(cls):
74 def _get_ui_settings(cls):
75 ret = RhodeCodeUi.query().all()
75 ret = RhodeCodeUi.query().all()
76
76
77 if not ret:
77 if not ret:
78 raise Exception('Could not get application ui settings !')
78 raise Exception('Could not get application ui settings !')
79 settings = {}
79 settings = {}
80 for each in ret:
80 for each in ret:
81 k = each.ui_key
81 k = each.ui_key
82 v = each.ui_value
82 v = each.ui_value
83 if k == '/':
83 if k == '/':
84 k = 'root_path'
84 k = 'root_path'
85
85
86 if k in ['push_ssl', 'publish', 'enabled']:
86 if k in ['push_ssl', 'publish', 'enabled']:
87 v = str2bool(v)
87 v = str2bool(v)
88
88
89 if k.find('.') != -1:
89 if k.find('.') != -1:
90 k = k.replace('.', '_')
90 k = k.replace('.', '_')
91
91
92 if each.ui_section in ['hooks', 'extensions']:
92 if each.ui_section in ['hooks', 'extensions']:
93 v = each.ui_active
93 v = each.ui_active
94
94
95 settings[each.ui_section + '_' + k] = v
95 settings[each.ui_section + '_' + k] = v
96 return settings
96 return settings
97
97
98 @classmethod
98 @classmethod
99 def _form_defaults(cls):
99 def _form_defaults(cls):
100 defaults = SettingsModel().get_all_settings()
100 defaults = SettingsModel().get_all_settings()
101 defaults.update(cls._get_ui_settings())
101 defaults.update(cls._get_ui_settings())
102
102
103 defaults.update({
103 defaults.update({
104 'new_svn_branch': '',
104 'new_svn_branch': '',
105 'new_svn_tag': '',
105 'new_svn_tag': '',
106 })
106 })
107 return defaults
107 return defaults
108
108
109 @LoginRequired()
109 @LoginRequired()
110 @HasPermissionAllDecorator('hg.admin')
110 @HasPermissionAllDecorator('hg.admin')
111 @view_config(
111 @view_config(
112 route_name='admin_settings_vcs', request_method='GET',
112 route_name='admin_settings_vcs', request_method='GET',
113 renderer='rhodecode:templates/admin/settings/settings.mako')
113 renderer='rhodecode:templates/admin/settings/settings.mako')
114 def settings_vcs(self):
114 def settings_vcs(self):
115 c = self.load_default_context()
115 c = self.load_default_context()
116 c.active = 'vcs'
116 c.active = 'vcs'
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120
120
121 settings = self.request.registry.settings
121 settings = self.request.registry.settings
122 c.svn_proxy_generate_config = settings[generate_config]
122 c.svn_proxy_generate_config = settings[generate_config]
123
123
124 defaults = self._form_defaults()
124 defaults = self._form_defaults()
125
125
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127
127
128 data = render('rhodecode:templates/admin/settings/settings.mako',
128 data = render('rhodecode:templates/admin/settings/settings.mako',
129 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
131 data,
131 data,
132 defaults=defaults,
132 defaults=defaults,
133 encoding="UTF-8",
133 encoding="UTF-8",
134 force_defaults=False
134 force_defaults=False
135 )
135 )
136 return Response(html)
136 return Response(html)
137
137
138 @LoginRequired()
138 @LoginRequired()
139 @HasPermissionAllDecorator('hg.admin')
139 @HasPermissionAllDecorator('hg.admin')
140 @CSRFRequired()
140 @CSRFRequired()
141 @view_config(
141 @view_config(
142 route_name='admin_settings_vcs_update', request_method='POST',
142 route_name='admin_settings_vcs_update', request_method='POST',
143 renderer='rhodecode:templates/admin/settings/settings.mako')
143 renderer='rhodecode:templates/admin/settings/settings.mako')
144 def settings_vcs_update(self):
144 def settings_vcs_update(self):
145 _ = self.request.translate
145 _ = self.request.translate
146 c = self.load_default_context()
146 c = self.load_default_context()
147 c.active = 'vcs'
147 c.active = 'vcs'
148
148
149 model = VcsSettingsModel()
149 model = VcsSettingsModel()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152
152
153 settings = self.request.registry.settings
153 settings = self.request.registry.settings
154 c.svn_proxy_generate_config = settings[generate_config]
154 c.svn_proxy_generate_config = settings[generate_config]
155
155
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
157
157
158 try:
158 try:
159 form_result = application_form.to_python(dict(self.request.POST))
159 form_result = application_form.to_python(dict(self.request.POST))
160 except formencode.Invalid as errors:
160 except formencode.Invalid as errors:
161 h.flash(
161 h.flash(
162 _("Some form inputs contain invalid data."),
162 _("Some form inputs contain invalid data."),
163 category='error')
163 category='error')
164 data = render('rhodecode:templates/admin/settings/settings.mako',
164 data = render('rhodecode:templates/admin/settings/settings.mako',
165 self._get_template_context(c), self.request)
165 self._get_template_context(c), self.request)
166 html = formencode.htmlfill.render(
166 html = formencode.htmlfill.render(
167 data,
167 data,
168 defaults=errors.value,
168 defaults=errors.value,
169 errors=errors.error_dict or {},
169 errors=errors.error_dict or {},
170 prefix_error=False,
170 prefix_error=False,
171 encoding="UTF-8",
171 encoding="UTF-8",
172 force_defaults=False
172 force_defaults=False
173 )
173 )
174 return Response(html)
174 return Response(html)
175
175
176 try:
176 try:
177 if c.visual.allow_repo_location_change:
177 if c.visual.allow_repo_location_change:
178 model.update_global_path_setting(form_result['paths_root_path'])
178 model.update_global_path_setting(form_result['paths_root_path'])
179
179
180 model.update_global_ssl_setting(form_result['web_push_ssl'])
180 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 model.update_global_hook_settings(form_result)
181 model.update_global_hook_settings(form_result)
182
182
183 model.create_or_update_global_svn_settings(form_result)
183 model.create_or_update_global_svn_settings(form_result)
184 model.create_or_update_global_hg_settings(form_result)
184 model.create_or_update_global_hg_settings(form_result)
185 model.create_or_update_global_git_settings(form_result)
185 model.create_or_update_global_git_settings(form_result)
186 model.create_or_update_global_pr_settings(form_result)
186 model.create_or_update_global_pr_settings(form_result)
187 except Exception:
187 except Exception:
188 log.exception("Exception while updating settings")
188 log.exception("Exception while updating settings")
189 h.flash(_('Error occurred during updating '
189 h.flash(_('Error occurred during updating '
190 'application settings'), category='error')
190 'application settings'), category='error')
191 else:
191 else:
192 Session().commit()
192 Session().commit()
193 h.flash(_('Updated VCS settings'), category='success')
193 h.flash(_('Updated VCS settings'), category='success')
194 raise HTTPFound(h.route_path('admin_settings_vcs'))
194 raise HTTPFound(h.route_path('admin_settings_vcs'))
195
195
196 data = render('rhodecode:templates/admin/settings/settings.mako',
196 data = render('rhodecode:templates/admin/settings/settings.mako',
197 self._get_template_context(c), self.request)
197 self._get_template_context(c), self.request)
198 html = formencode.htmlfill.render(
198 html = formencode.htmlfill.render(
199 data,
199 data,
200 defaults=self._form_defaults(),
200 defaults=self._form_defaults(),
201 encoding="UTF-8",
201 encoding="UTF-8",
202 force_defaults=False
202 force_defaults=False
203 )
203 )
204 return Response(html)
204 return Response(html)
205
205
206 @LoginRequired()
206 @LoginRequired()
207 @HasPermissionAllDecorator('hg.admin')
207 @HasPermissionAllDecorator('hg.admin')
208 @CSRFRequired()
208 @CSRFRequired()
209 @view_config(
209 @view_config(
210 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
210 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 renderer='json_ext', xhr=True)
211 renderer='json_ext', xhr=True)
212 def settings_vcs_delete_svn_pattern(self):
212 def settings_vcs_delete_svn_pattern(self):
213 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
213 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 model = VcsSettingsModel()
214 model = VcsSettingsModel()
215 try:
215 try:
216 model.delete_global_svn_pattern(delete_pattern_id)
216 model.delete_global_svn_pattern(delete_pattern_id)
217 except SettingNotFound:
217 except SettingNotFound:
218 log.exception(
218 log.exception(
219 'Failed to delete svn_pattern with id %s', delete_pattern_id)
219 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 raise HTTPNotFound()
220 raise HTTPNotFound()
221
221
222 Session().commit()
222 Session().commit()
223 return True
223 return True
224
224
225 @LoginRequired()
225 @LoginRequired()
226 @HasPermissionAllDecorator('hg.admin')
226 @HasPermissionAllDecorator('hg.admin')
227 @view_config(
227 @view_config(
228 route_name='admin_settings_mapping', request_method='GET',
228 route_name='admin_settings_mapping', request_method='GET',
229 renderer='rhodecode:templates/admin/settings/settings.mako')
229 renderer='rhodecode:templates/admin/settings/settings.mako')
230 def settings_mapping(self):
230 def settings_mapping(self):
231 c = self.load_default_context()
231 c = self.load_default_context()
232 c.active = 'mapping'
232 c.active = 'mapping'
233
233
234 data = render('rhodecode:templates/admin/settings/settings.mako',
234 data = render('rhodecode:templates/admin/settings/settings.mako',
235 self._get_template_context(c), self.request)
235 self._get_template_context(c), self.request)
236 html = formencode.htmlfill.render(
236 html = formencode.htmlfill.render(
237 data,
237 data,
238 defaults=self._form_defaults(),
238 defaults=self._form_defaults(),
239 encoding="UTF-8",
239 encoding="UTF-8",
240 force_defaults=False
240 force_defaults=False
241 )
241 )
242 return Response(html)
242 return Response(html)
243
243
244 @LoginRequired()
244 @LoginRequired()
245 @HasPermissionAllDecorator('hg.admin')
245 @HasPermissionAllDecorator('hg.admin')
246 @CSRFRequired()
246 @CSRFRequired()
247 @view_config(
247 @view_config(
248 route_name='admin_settings_mapping_update', request_method='POST',
248 route_name='admin_settings_mapping_update', request_method='POST',
249 renderer='rhodecode:templates/admin/settings/settings.mako')
249 renderer='rhodecode:templates/admin/settings/settings.mako')
250 def settings_mapping_update(self):
250 def settings_mapping_update(self):
251 _ = self.request.translate
251 _ = self.request.translate
252 c = self.load_default_context()
252 c = self.load_default_context()
253 c.active = 'mapping'
253 c.active = 'mapping'
254 rm_obsolete = self.request.POST.get('destroy', False)
254 rm_obsolete = self.request.POST.get('destroy', False)
255 invalidate_cache = self.request.POST.get('invalidate', False)
255 invalidate_cache = self.request.POST.get('invalidate', False)
256 log.debug(
256 log.debug(
257 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
257 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
258
258
259 if invalidate_cache:
259 if invalidate_cache:
260 log.debug('invalidating all repositories cache')
260 log.debug('invalidating all repositories cache')
261 for repo in Repository.get_all():
261 for repo in Repository.get_all():
262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263
263
264 filesystem_repos = ScmModel().repo_scan()
264 filesystem_repos = ScmModel().repo_scan()
265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
266 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 h.flash(_('Repositories successfully '
267 h.flash(_('Repositories successfully '
268 'rescanned added: %s ; removed: %s') %
268 'rescanned added: %s ; removed: %s') %
269 (_repr(added), _repr(removed)),
269 (_repr(added), _repr(removed)),
270 category='success')
270 category='success')
271 raise HTTPFound(h.route_path('admin_settings_mapping'))
271 raise HTTPFound(h.route_path('admin_settings_mapping'))
272
272
273 @LoginRequired()
273 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
274 @HasPermissionAllDecorator('hg.admin')
275 @view_config(
275 @view_config(
276 route_name='admin_settings', request_method='GET',
276 route_name='admin_settings', request_method='GET',
277 renderer='rhodecode:templates/admin/settings/settings.mako')
277 renderer='rhodecode:templates/admin/settings/settings.mako')
278 @view_config(
278 @view_config(
279 route_name='admin_settings_global', request_method='GET',
279 route_name='admin_settings_global', request_method='GET',
280 renderer='rhodecode:templates/admin/settings/settings.mako')
280 renderer='rhodecode:templates/admin/settings/settings.mako')
281 def settings_global(self):
281 def settings_global(self):
282 c = self.load_default_context()
282 c = self.load_default_context()
283 c.active = 'global'
283 c.active = 'global'
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 .get_personal_group_name_pattern()
285 .get_personal_group_name_pattern()
286
286
287 data = render('rhodecode:templates/admin/settings/settings.mako',
287 data = render('rhodecode:templates/admin/settings/settings.mako',
288 self._get_template_context(c), self.request)
288 self._get_template_context(c), self.request)
289 html = formencode.htmlfill.render(
289 html = formencode.htmlfill.render(
290 data,
290 data,
291 defaults=self._form_defaults(),
291 defaults=self._form_defaults(),
292 encoding="UTF-8",
292 encoding="UTF-8",
293 force_defaults=False
293 force_defaults=False
294 )
294 )
295 return Response(html)
295 return Response(html)
296
296
297 @LoginRequired()
297 @LoginRequired()
298 @HasPermissionAllDecorator('hg.admin')
298 @HasPermissionAllDecorator('hg.admin')
299 @CSRFRequired()
299 @CSRFRequired()
300 @view_config(
300 @view_config(
301 route_name='admin_settings_update', request_method='POST',
301 route_name='admin_settings_update', request_method='POST',
302 renderer='rhodecode:templates/admin/settings/settings.mako')
302 renderer='rhodecode:templates/admin/settings/settings.mako')
303 @view_config(
303 @view_config(
304 route_name='admin_settings_global_update', request_method='POST',
304 route_name='admin_settings_global_update', request_method='POST',
305 renderer='rhodecode:templates/admin/settings/settings.mako')
305 renderer='rhodecode:templates/admin/settings/settings.mako')
306 def settings_global_update(self):
306 def settings_global_update(self):
307 _ = self.request.translate
307 _ = self.request.translate
308 c = self.load_default_context()
308 c = self.load_default_context()
309 c.active = 'global'
309 c.active = 'global'
310 c.personal_repo_group_default_pattern = RepoGroupModel()\
310 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 .get_personal_group_name_pattern()
311 .get_personal_group_name_pattern()
312 application_form = ApplicationSettingsForm(self.request.translate)()
312 application_form = ApplicationSettingsForm(self.request.translate)()
313 try:
313 try:
314 form_result = application_form.to_python(dict(self.request.POST))
314 form_result = application_form.to_python(dict(self.request.POST))
315 except formencode.Invalid as errors:
315 except formencode.Invalid as errors:
316 h.flash(
316 h.flash(
317 _("Some form inputs contain invalid data."),
317 _("Some form inputs contain invalid data."),
318 category='error')
318 category='error')
319 data = render('rhodecode:templates/admin/settings/settings.mako',
319 data = render('rhodecode:templates/admin/settings/settings.mako',
320 self._get_template_context(c), self.request)
320 self._get_template_context(c), self.request)
321 html = formencode.htmlfill.render(
321 html = formencode.htmlfill.render(
322 data,
322 data,
323 defaults=errors.value,
323 defaults=errors.value,
324 errors=errors.error_dict or {},
324 errors=errors.error_dict or {},
325 prefix_error=False,
325 prefix_error=False,
326 encoding="UTF-8",
326 encoding="UTF-8",
327 force_defaults=False
327 force_defaults=False
328 )
328 )
329 return Response(html)
329 return Response(html)
330
330
331 settings = [
331 settings = [
332 ('title', 'rhodecode_title', 'unicode'),
332 ('title', 'rhodecode_title', 'unicode'),
333 ('realm', 'rhodecode_realm', 'unicode'),
333 ('realm', 'rhodecode_realm', 'unicode'),
334 ('pre_code', 'rhodecode_pre_code', 'unicode'),
334 ('pre_code', 'rhodecode_pre_code', 'unicode'),
335 ('post_code', 'rhodecode_post_code', 'unicode'),
335 ('post_code', 'rhodecode_post_code', 'unicode'),
336 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
336 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
337 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
337 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
338 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
338 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
339 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
339 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
340 ]
340 ]
341 try:
341 try:
342 for setting, form_key, type_ in settings:
342 for setting, form_key, type_ in settings:
343 sett = SettingsModel().create_or_update_setting(
343 sett = SettingsModel().create_or_update_setting(
344 setting, form_result[form_key], type_)
344 setting, form_result[form_key], type_)
345 Session().add(sett)
345 Session().add(sett)
346
346
347 Session().commit()
347 Session().commit()
348 SettingsModel().invalidate_settings_cache()
348 SettingsModel().invalidate_settings_cache()
349 h.flash(_('Updated application settings'), category='success')
349 h.flash(_('Updated application settings'), category='success')
350 except Exception:
350 except Exception:
351 log.exception("Exception while updating application settings")
351 log.exception("Exception while updating application settings")
352 h.flash(
352 h.flash(
353 _('Error occurred during updating application settings'),
353 _('Error occurred during updating application settings'),
354 category='error')
354 category='error')
355
355
356 raise HTTPFound(h.route_path('admin_settings_global'))
356 raise HTTPFound(h.route_path('admin_settings_global'))
357
357
358 @LoginRequired()
358 @LoginRequired()
359 @HasPermissionAllDecorator('hg.admin')
359 @HasPermissionAllDecorator('hg.admin')
360 @view_config(
360 @view_config(
361 route_name='admin_settings_visual', request_method='GET',
361 route_name='admin_settings_visual', request_method='GET',
362 renderer='rhodecode:templates/admin/settings/settings.mako')
362 renderer='rhodecode:templates/admin/settings/settings.mako')
363 def settings_visual(self):
363 def settings_visual(self):
364 c = self.load_default_context()
364 c = self.load_default_context()
365 c.active = 'visual'
365 c.active = 'visual'
366
366
367 data = render('rhodecode:templates/admin/settings/settings.mako',
367 data = render('rhodecode:templates/admin/settings/settings.mako',
368 self._get_template_context(c), self.request)
368 self._get_template_context(c), self.request)
369 html = formencode.htmlfill.render(
369 html = formencode.htmlfill.render(
370 data,
370 data,
371 defaults=self._form_defaults(),
371 defaults=self._form_defaults(),
372 encoding="UTF-8",
372 encoding="UTF-8",
373 force_defaults=False
373 force_defaults=False
374 )
374 )
375 return Response(html)
375 return Response(html)
376
376
377 @LoginRequired()
377 @LoginRequired()
378 @HasPermissionAllDecorator('hg.admin')
378 @HasPermissionAllDecorator('hg.admin')
379 @CSRFRequired()
379 @CSRFRequired()
380 @view_config(
380 @view_config(
381 route_name='admin_settings_visual_update', request_method='POST',
381 route_name='admin_settings_visual_update', request_method='POST',
382 renderer='rhodecode:templates/admin/settings/settings.mako')
382 renderer='rhodecode:templates/admin/settings/settings.mako')
383 def settings_visual_update(self):
383 def settings_visual_update(self):
384 _ = self.request.translate
384 _ = self.request.translate
385 c = self.load_default_context()
385 c = self.load_default_context()
386 c.active = 'visual'
386 c.active = 'visual'
387 application_form = ApplicationVisualisationForm(self.request.translate)()
387 application_form = ApplicationVisualisationForm(self.request.translate)()
388 try:
388 try:
389 form_result = application_form.to_python(dict(self.request.POST))
389 form_result = application_form.to_python(dict(self.request.POST))
390 except formencode.Invalid as errors:
390 except formencode.Invalid as errors:
391 h.flash(
391 h.flash(
392 _("Some form inputs contain invalid data."),
392 _("Some form inputs contain invalid data."),
393 category='error')
393 category='error')
394 data = render('rhodecode:templates/admin/settings/settings.mako',
394 data = render('rhodecode:templates/admin/settings/settings.mako',
395 self._get_template_context(c), self.request)
395 self._get_template_context(c), self.request)
396 html = formencode.htmlfill.render(
396 html = formencode.htmlfill.render(
397 data,
397 data,
398 defaults=errors.value,
398 defaults=errors.value,
399 errors=errors.error_dict or {},
399 errors=errors.error_dict or {},
400 prefix_error=False,
400 prefix_error=False,
401 encoding="UTF-8",
401 encoding="UTF-8",
402 force_defaults=False
402 force_defaults=False
403 )
403 )
404 return Response(html)
404 return Response(html)
405
405
406 try:
406 try:
407 settings = [
407 settings = [
408 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
408 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
409 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
409 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
410 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
410 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
411 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
411 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
412 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
412 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
413 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
413 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
414 ('show_version', 'rhodecode_show_version', 'bool'),
414 ('show_version', 'rhodecode_show_version', 'bool'),
415 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
415 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
416 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
416 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
417 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
417 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
418 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
418 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
419 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
419 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
420 ('support_url', 'rhodecode_support_url', 'unicode'),
420 ('support_url', 'rhodecode_support_url', 'unicode'),
421 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
421 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
422 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
422 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
423 ]
423 ]
424 for setting, form_key, type_ in settings:
424 for setting, form_key, type_ in settings:
425 sett = SettingsModel().create_or_update_setting(
425 sett = SettingsModel().create_or_update_setting(
426 setting, form_result[form_key], type_)
426 setting, form_result[form_key], type_)
427 Session().add(sett)
427 Session().add(sett)
428
428
429 Session().commit()
429 Session().commit()
430 SettingsModel().invalidate_settings_cache()
430 SettingsModel().invalidate_settings_cache()
431 h.flash(_('Updated visualisation settings'), category='success')
431 h.flash(_('Updated visualisation settings'), category='success')
432 except Exception:
432 except Exception:
433 log.exception("Exception updating visualization settings")
433 log.exception("Exception updating visualization settings")
434 h.flash(_('Error occurred during updating '
434 h.flash(_('Error occurred during updating '
435 'visualisation settings'),
435 'visualisation settings'),
436 category='error')
436 category='error')
437
437
438 raise HTTPFound(h.route_path('admin_settings_visual'))
438 raise HTTPFound(h.route_path('admin_settings_visual'))
439
439
440 @LoginRequired()
440 @LoginRequired()
441 @HasPermissionAllDecorator('hg.admin')
441 @HasPermissionAllDecorator('hg.admin')
442 @view_config(
442 @view_config(
443 route_name='admin_settings_issuetracker', request_method='GET',
443 route_name='admin_settings_issuetracker', request_method='GET',
444 renderer='rhodecode:templates/admin/settings/settings.mako')
444 renderer='rhodecode:templates/admin/settings/settings.mako')
445 def settings_issuetracker(self):
445 def settings_issuetracker(self):
446 c = self.load_default_context()
446 c = self.load_default_context()
447 c.active = 'issuetracker'
447 c.active = 'issuetracker'
448 defaults = c.rc_config
448 defaults = c.rc_config
449
449
450 entry_key = 'rhodecode_issuetracker_pat_'
450 entry_key = 'rhodecode_issuetracker_pat_'
451
451
452 c.issuetracker_entries = {}
452 c.issuetracker_entries = {}
453 for k, v in defaults.items():
453 for k, v in defaults.items():
454 if k.startswith(entry_key):
454 if k.startswith(entry_key):
455 uid = k[len(entry_key):]
455 uid = k[len(entry_key):]
456 c.issuetracker_entries[uid] = None
456 c.issuetracker_entries[uid] = None
457
457
458 for uid in c.issuetracker_entries:
458 for uid in c.issuetracker_entries:
459 c.issuetracker_entries[uid] = AttributeDict({
459 c.issuetracker_entries[uid] = AttributeDict({
460 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
460 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
461 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
461 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
462 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
462 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
463 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
463 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
464 })
464 })
465
465
466 return self._get_template_context(c)
466 return self._get_template_context(c)
467
467
468 @LoginRequired()
468 @LoginRequired()
469 @HasPermissionAllDecorator('hg.admin')
469 @HasPermissionAllDecorator('hg.admin')
470 @CSRFRequired()
470 @CSRFRequired()
471 @view_config(
471 @view_config(
472 route_name='admin_settings_issuetracker_test', request_method='POST',
472 route_name='admin_settings_issuetracker_test', request_method='POST',
473 renderer='string', xhr=True)
473 renderer='string', xhr=True)
474 def settings_issuetracker_test(self):
474 def settings_issuetracker_test(self):
475 return h.urlify_commit_message(
475 return h.urlify_commit_message(
476 self.request.POST.get('test_text', ''),
476 self.request.POST.get('test_text', ''),
477 'repo_group/test_repo1')
477 'repo_group/test_repo1')
478
478
479 @LoginRequired()
479 @LoginRequired()
480 @HasPermissionAllDecorator('hg.admin')
480 @HasPermissionAllDecorator('hg.admin')
481 @CSRFRequired()
481 @CSRFRequired()
482 @view_config(
482 @view_config(
483 route_name='admin_settings_issuetracker_update', request_method='POST',
483 route_name='admin_settings_issuetracker_update', request_method='POST',
484 renderer='rhodecode:templates/admin/settings/settings.mako')
484 renderer='rhodecode:templates/admin/settings/settings.mako')
485 def settings_issuetracker_update(self):
485 def settings_issuetracker_update(self):
486 _ = self.request.translate
486 _ = self.request.translate
487 self.load_default_context()
487 self.load_default_context()
488 settings_model = IssueTrackerSettingsModel()
488 settings_model = IssueTrackerSettingsModel()
489
489
490 try:
490 try:
491 form = IssueTrackerPatternsForm(self.request.translate)()
491 form = IssueTrackerPatternsForm(self.request.translate)()
492 data = form.to_python(self.request.POST)
492 data = form.to_python(self.request.POST)
493 except formencode.Invalid as errors:
493 except formencode.Invalid as errors:
494 log.exception('Failed to add new pattern')
494 log.exception('Failed to add new pattern')
495 error = errors
495 error = errors
496 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
496 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
497 category='error')
497 category='error')
498 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
498 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
499
499
500 if data:
500 if data:
501 for uid in data.get('delete_patterns', []):
501 for uid in data.get('delete_patterns', []):
502 settings_model.delete_entries(uid)
502 settings_model.delete_entries(uid)
503
503
504 for pattern in data.get('patterns', []):
504 for pattern in data.get('patterns', []):
505 for setting, value, type_ in pattern:
505 for setting, value, type_ in pattern:
506 sett = settings_model.create_or_update_setting(
506 sett = settings_model.create_or_update_setting(
507 setting, value, type_)
507 setting, value, type_)
508 Session().add(sett)
508 Session().add(sett)
509
509
510 Session().commit()
510 Session().commit()
511
511
512 SettingsModel().invalidate_settings_cache()
512 SettingsModel().invalidate_settings_cache()
513 h.flash(_('Updated issue tracker entries'), category='success')
513 h.flash(_('Updated issue tracker entries'), category='success')
514 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
514 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
515
515
516 @LoginRequired()
516 @LoginRequired()
517 @HasPermissionAllDecorator('hg.admin')
517 @HasPermissionAllDecorator('hg.admin')
518 @CSRFRequired()
518 @CSRFRequired()
519 @view_config(
519 @view_config(
520 route_name='admin_settings_issuetracker_delete', request_method='POST',
520 route_name='admin_settings_issuetracker_delete', request_method='POST',
521 renderer='rhodecode:templates/admin/settings/settings.mako')
521 renderer='json_ext', xhr=True)
522 def settings_issuetracker_delete(self):
522 def settings_issuetracker_delete(self):
523 _ = self.request.translate
523 _ = self.request.translate
524 self.load_default_context()
524 self.load_default_context()
525 uid = self.request.POST.get('uid')
525 uid = self.request.POST.get('uid')
526 try:
526 try:
527 IssueTrackerSettingsModel().delete_entries(uid)
527 IssueTrackerSettingsModel().delete_entries(uid)
528 except Exception:
528 except Exception:
529 log.exception('Failed to delete issue tracker setting %s', uid)
529 log.exception('Failed to delete issue tracker setting %s', uid)
530 raise HTTPNotFound()
530 raise HTTPNotFound()
531 h.flash(_('Removed issue tracker entry'), category='success')
531
532 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
532 SettingsModel().invalidate_settings_cache()
533 h.flash(_('Removed issue tracker entry.'), category='success')
534
535 return {'deleted': uid}
533
536
534 @LoginRequired()
537 @LoginRequired()
535 @HasPermissionAllDecorator('hg.admin')
538 @HasPermissionAllDecorator('hg.admin')
536 @view_config(
539 @view_config(
537 route_name='admin_settings_email', request_method='GET',
540 route_name='admin_settings_email', request_method='GET',
538 renderer='rhodecode:templates/admin/settings/settings.mako')
541 renderer='rhodecode:templates/admin/settings/settings.mako')
539 def settings_email(self):
542 def settings_email(self):
540 c = self.load_default_context()
543 c = self.load_default_context()
541 c.active = 'email'
544 c.active = 'email'
542 c.rhodecode_ini = rhodecode.CONFIG
545 c.rhodecode_ini = rhodecode.CONFIG
543
546
544 data = render('rhodecode:templates/admin/settings/settings.mako',
547 data = render('rhodecode:templates/admin/settings/settings.mako',
545 self._get_template_context(c), self.request)
548 self._get_template_context(c), self.request)
546 html = formencode.htmlfill.render(
549 html = formencode.htmlfill.render(
547 data,
550 data,
548 defaults=self._form_defaults(),
551 defaults=self._form_defaults(),
549 encoding="UTF-8",
552 encoding="UTF-8",
550 force_defaults=False
553 force_defaults=False
551 )
554 )
552 return Response(html)
555 return Response(html)
553
556
554 @LoginRequired()
557 @LoginRequired()
555 @HasPermissionAllDecorator('hg.admin')
558 @HasPermissionAllDecorator('hg.admin')
556 @CSRFRequired()
559 @CSRFRequired()
557 @view_config(
560 @view_config(
558 route_name='admin_settings_email_update', request_method='POST',
561 route_name='admin_settings_email_update', request_method='POST',
559 renderer='rhodecode:templates/admin/settings/settings.mako')
562 renderer='rhodecode:templates/admin/settings/settings.mako')
560 def settings_email_update(self):
563 def settings_email_update(self):
561 _ = self.request.translate
564 _ = self.request.translate
562 c = self.load_default_context()
565 c = self.load_default_context()
563 c.active = 'email'
566 c.active = 'email'
564
567
565 test_email = self.request.POST.get('test_email')
568 test_email = self.request.POST.get('test_email')
566
569
567 if not test_email:
570 if not test_email:
568 h.flash(_('Please enter email address'), category='error')
571 h.flash(_('Please enter email address'), category='error')
569 raise HTTPFound(h.route_path('admin_settings_email'))
572 raise HTTPFound(h.route_path('admin_settings_email'))
570
573
571 email_kwargs = {
574 email_kwargs = {
572 'date': datetime.datetime.now(),
575 'date': datetime.datetime.now(),
573 'user': c.rhodecode_user,
576 'user': c.rhodecode_user,
574 'rhodecode_version': c.rhodecode_version
577 'rhodecode_version': c.rhodecode_version
575 }
578 }
576
579
577 (subject, headers, email_body,
580 (subject, headers, email_body,
578 email_body_plaintext) = EmailNotificationModel().render_email(
581 email_body_plaintext) = EmailNotificationModel().render_email(
579 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
582 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
580
583
581 recipients = [test_email] if test_email else None
584 recipients = [test_email] if test_email else None
582
585
583 run_task(tasks.send_email, recipients, subject,
586 run_task(tasks.send_email, recipients, subject,
584 email_body_plaintext, email_body)
587 email_body_plaintext, email_body)
585
588
586 h.flash(_('Send email task created'), category='success')
589 h.flash(_('Send email task created'), category='success')
587 raise HTTPFound(h.route_path('admin_settings_email'))
590 raise HTTPFound(h.route_path('admin_settings_email'))
588
591
589 @LoginRequired()
592 @LoginRequired()
590 @HasPermissionAllDecorator('hg.admin')
593 @HasPermissionAllDecorator('hg.admin')
591 @view_config(
594 @view_config(
592 route_name='admin_settings_hooks', request_method='GET',
595 route_name='admin_settings_hooks', request_method='GET',
593 renderer='rhodecode:templates/admin/settings/settings.mako')
596 renderer='rhodecode:templates/admin/settings/settings.mako')
594 def settings_hooks(self):
597 def settings_hooks(self):
595 c = self.load_default_context()
598 c = self.load_default_context()
596 c.active = 'hooks'
599 c.active = 'hooks'
597
600
598 model = SettingsModel()
601 model = SettingsModel()
599 c.hooks = model.get_builtin_hooks()
602 c.hooks = model.get_builtin_hooks()
600 c.custom_hooks = model.get_custom_hooks()
603 c.custom_hooks = model.get_custom_hooks()
601
604
602 data = render('rhodecode:templates/admin/settings/settings.mako',
605 data = render('rhodecode:templates/admin/settings/settings.mako',
603 self._get_template_context(c), self.request)
606 self._get_template_context(c), self.request)
604 html = formencode.htmlfill.render(
607 html = formencode.htmlfill.render(
605 data,
608 data,
606 defaults=self._form_defaults(),
609 defaults=self._form_defaults(),
607 encoding="UTF-8",
610 encoding="UTF-8",
608 force_defaults=False
611 force_defaults=False
609 )
612 )
610 return Response(html)
613 return Response(html)
611
614
612 @LoginRequired()
615 @LoginRequired()
613 @HasPermissionAllDecorator('hg.admin')
616 @HasPermissionAllDecorator('hg.admin')
614 @CSRFRequired()
617 @CSRFRequired()
615 @view_config(
618 @view_config(
616 route_name='admin_settings_hooks_update', request_method='POST',
619 route_name='admin_settings_hooks_update', request_method='POST',
617 renderer='rhodecode:templates/admin/settings/settings.mako')
620 renderer='rhodecode:templates/admin/settings/settings.mako')
618 @view_config(
621 @view_config(
619 route_name='admin_settings_hooks_delete', request_method='POST',
622 route_name='admin_settings_hooks_delete', request_method='POST',
620 renderer='rhodecode:templates/admin/settings/settings.mako')
623 renderer='rhodecode:templates/admin/settings/settings.mako')
621 def settings_hooks_update(self):
624 def settings_hooks_update(self):
622 _ = self.request.translate
625 _ = self.request.translate
623 c = self.load_default_context()
626 c = self.load_default_context()
624 c.active = 'hooks'
627 c.active = 'hooks'
625 if c.visual.allow_custom_hooks_settings:
628 if c.visual.allow_custom_hooks_settings:
626 ui_key = self.request.POST.get('new_hook_ui_key')
629 ui_key = self.request.POST.get('new_hook_ui_key')
627 ui_value = self.request.POST.get('new_hook_ui_value')
630 ui_value = self.request.POST.get('new_hook_ui_value')
628
631
629 hook_id = self.request.POST.get('hook_id')
632 hook_id = self.request.POST.get('hook_id')
630 new_hook = False
633 new_hook = False
631
634
632 model = SettingsModel()
635 model = SettingsModel()
633 try:
636 try:
634 if ui_value and ui_key:
637 if ui_value and ui_key:
635 model.create_or_update_hook(ui_key, ui_value)
638 model.create_or_update_hook(ui_key, ui_value)
636 h.flash(_('Added new hook'), category='success')
639 h.flash(_('Added new hook'), category='success')
637 new_hook = True
640 new_hook = True
638 elif hook_id:
641 elif hook_id:
639 RhodeCodeUi.delete(hook_id)
642 RhodeCodeUi.delete(hook_id)
640 Session().commit()
643 Session().commit()
641
644
642 # check for edits
645 # check for edits
643 update = False
646 update = False
644 _d = self.request.POST.dict_of_lists()
647 _d = self.request.POST.dict_of_lists()
645 for k, v in zip(_d.get('hook_ui_key', []),
648 for k, v in zip(_d.get('hook_ui_key', []),
646 _d.get('hook_ui_value_new', [])):
649 _d.get('hook_ui_value_new', [])):
647 model.create_or_update_hook(k, v)
650 model.create_or_update_hook(k, v)
648 update = True
651 update = True
649
652
650 if update and not new_hook:
653 if update and not new_hook:
651 h.flash(_('Updated hooks'), category='success')
654 h.flash(_('Updated hooks'), category='success')
652 Session().commit()
655 Session().commit()
653 except Exception:
656 except Exception:
654 log.exception("Exception during hook creation")
657 log.exception("Exception during hook creation")
655 h.flash(_('Error occurred during hook creation'),
658 h.flash(_('Error occurred during hook creation'),
656 category='error')
659 category='error')
657
660
658 raise HTTPFound(h.route_path('admin_settings_hooks'))
661 raise HTTPFound(h.route_path('admin_settings_hooks'))
659
662
660 @LoginRequired()
663 @LoginRequired()
661 @HasPermissionAllDecorator('hg.admin')
664 @HasPermissionAllDecorator('hg.admin')
662 @view_config(
665 @view_config(
663 route_name='admin_settings_search', request_method='GET',
666 route_name='admin_settings_search', request_method='GET',
664 renderer='rhodecode:templates/admin/settings/settings.mako')
667 renderer='rhodecode:templates/admin/settings/settings.mako')
665 def settings_search(self):
668 def settings_search(self):
666 c = self.load_default_context()
669 c = self.load_default_context()
667 c.active = 'search'
670 c.active = 'search'
668
671
669 c.searcher = searcher_from_config(self.request.registry.settings)
672 c.searcher = searcher_from_config(self.request.registry.settings)
670 c.statistics = c.searcher.statistics(self.request.translate)
673 c.statistics = c.searcher.statistics(self.request.translate)
671
674
672 return self._get_template_context(c)
675 return self._get_template_context(c)
673
676
674 @LoginRequired()
677 @LoginRequired()
675 @HasPermissionAllDecorator('hg.admin')
678 @HasPermissionAllDecorator('hg.admin')
676 @view_config(
679 @view_config(
677 route_name='admin_settings_automation', request_method='GET',
680 route_name='admin_settings_automation', request_method='GET',
678 renderer='rhodecode:templates/admin/settings/settings.mako')
681 renderer='rhodecode:templates/admin/settings/settings.mako')
679 def settings_automation(self):
682 def settings_automation(self):
680 c = self.load_default_context()
683 c = self.load_default_context()
681 c.active = 'automation'
684 c.active = 'automation'
682
685
683 return self._get_template_context(c)
686 return self._get_template_context(c)
684
687
685 @LoginRequired()
688 @LoginRequired()
686 @HasPermissionAllDecorator('hg.admin')
689 @HasPermissionAllDecorator('hg.admin')
687 @view_config(
690 @view_config(
688 route_name='admin_settings_labs', request_method='GET',
691 route_name='admin_settings_labs', request_method='GET',
689 renderer='rhodecode:templates/admin/settings/settings.mako')
692 renderer='rhodecode:templates/admin/settings/settings.mako')
690 def settings_labs(self):
693 def settings_labs(self):
691 c = self.load_default_context()
694 c = self.load_default_context()
692 if not c.labs_active:
695 if not c.labs_active:
693 raise HTTPFound(h.route_path('admin_settings'))
696 raise HTTPFound(h.route_path('admin_settings'))
694
697
695 c.active = 'labs'
698 c.active = 'labs'
696 c.lab_settings = _LAB_SETTINGS
699 c.lab_settings = _LAB_SETTINGS
697
700
698 data = render('rhodecode:templates/admin/settings/settings.mako',
701 data = render('rhodecode:templates/admin/settings/settings.mako',
699 self._get_template_context(c), self.request)
702 self._get_template_context(c), self.request)
700 html = formencode.htmlfill.render(
703 html = formencode.htmlfill.render(
701 data,
704 data,
702 defaults=self._form_defaults(),
705 defaults=self._form_defaults(),
703 encoding="UTF-8",
706 encoding="UTF-8",
704 force_defaults=False
707 force_defaults=False
705 )
708 )
706 return Response(html)
709 return Response(html)
707
710
708 @LoginRequired()
711 @LoginRequired()
709 @HasPermissionAllDecorator('hg.admin')
712 @HasPermissionAllDecorator('hg.admin')
710 @CSRFRequired()
713 @CSRFRequired()
711 @view_config(
714 @view_config(
712 route_name='admin_settings_labs_update', request_method='POST',
715 route_name='admin_settings_labs_update', request_method='POST',
713 renderer='rhodecode:templates/admin/settings/settings.mako')
716 renderer='rhodecode:templates/admin/settings/settings.mako')
714 def settings_labs_update(self):
717 def settings_labs_update(self):
715 _ = self.request.translate
718 _ = self.request.translate
716 c = self.load_default_context()
719 c = self.load_default_context()
717 c.active = 'labs'
720 c.active = 'labs'
718
721
719 application_form = LabsSettingsForm(self.request.translate)()
722 application_form = LabsSettingsForm(self.request.translate)()
720 try:
723 try:
721 form_result = application_form.to_python(dict(self.request.POST))
724 form_result = application_form.to_python(dict(self.request.POST))
722 except formencode.Invalid as errors:
725 except formencode.Invalid as errors:
723 h.flash(
726 h.flash(
724 _("Some form inputs contain invalid data."),
727 _("Some form inputs contain invalid data."),
725 category='error')
728 category='error')
726 data = render('rhodecode:templates/admin/settings/settings.mako',
729 data = render('rhodecode:templates/admin/settings/settings.mako',
727 self._get_template_context(c), self.request)
730 self._get_template_context(c), self.request)
728 html = formencode.htmlfill.render(
731 html = formencode.htmlfill.render(
729 data,
732 data,
730 defaults=errors.value,
733 defaults=errors.value,
731 errors=errors.error_dict or {},
734 errors=errors.error_dict or {},
732 prefix_error=False,
735 prefix_error=False,
733 encoding="UTF-8",
736 encoding="UTF-8",
734 force_defaults=False
737 force_defaults=False
735 )
738 )
736 return Response(html)
739 return Response(html)
737
740
738 try:
741 try:
739 session = Session()
742 session = Session()
740 for setting in _LAB_SETTINGS:
743 for setting in _LAB_SETTINGS:
741 setting_name = setting.key[len('rhodecode_'):]
744 setting_name = setting.key[len('rhodecode_'):]
742 sett = SettingsModel().create_or_update_setting(
745 sett = SettingsModel().create_or_update_setting(
743 setting_name, form_result[setting.key], setting.type)
746 setting_name, form_result[setting.key], setting.type)
744 session.add(sett)
747 session.add(sett)
745
748
746 except Exception:
749 except Exception:
747 log.exception('Exception while updating lab settings')
750 log.exception('Exception while updating lab settings')
748 h.flash(_('Error occurred during updating labs settings'),
751 h.flash(_('Error occurred during updating labs settings'),
749 category='error')
752 category='error')
750 else:
753 else:
751 Session().commit()
754 Session().commit()
752 SettingsModel().invalidate_settings_cache()
755 SettingsModel().invalidate_settings_cache()
753 h.flash(_('Updated Labs settings'), category='success')
756 h.flash(_('Updated Labs settings'), category='success')
754 raise HTTPFound(h.route_path('admin_settings_labs'))
757 raise HTTPFound(h.route_path('admin_settings_labs'))
755
758
756 data = render('rhodecode:templates/admin/settings/settings.mako',
759 data = render('rhodecode:templates/admin/settings/settings.mako',
757 self._get_template_context(c), self.request)
760 self._get_template_context(c), self.request)
758 html = formencode.htmlfill.render(
761 html = formencode.htmlfill.render(
759 data,
762 data,
760 defaults=self._form_defaults(),
763 defaults=self._form_defaults(),
761 encoding="UTF-8",
764 encoding="UTF-8",
762 force_defaults=False
765 force_defaults=False
763 )
766 )
764 return Response(html)
767 return Response(html)
765
768
766
769
767 # :param key: name of the setting including the 'rhodecode_' prefix
770 # :param key: name of the setting including the 'rhodecode_' prefix
768 # :param type: the RhodeCodeSetting type to use.
771 # :param type: the RhodeCodeSetting type to use.
769 # :param group: the i18ned group in which we should dispaly this setting
772 # :param group: the i18ned group in which we should dispaly this setting
770 # :param label: the i18ned label we should display for this setting
773 # :param label: the i18ned label we should display for this setting
771 # :param help: the i18ned help we should dispaly for this setting
774 # :param help: the i18ned help we should dispaly for this setting
772 LabSetting = collections.namedtuple(
775 LabSetting = collections.namedtuple(
773 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
776 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
774
777
775
778
776 # This list has to be kept in sync with the form
779 # This list has to be kept in sync with the form
777 # rhodecode.model.forms.LabsSettingsForm.
780 # rhodecode.model.forms.LabsSettingsForm.
778 _LAB_SETTINGS = [
781 _LAB_SETTINGS = [
779
782
780 ]
783 ]
@@ -1,148 +1,149 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.utils2 import md5
23 from rhodecode.lib.utils2 import md5
24 from rhodecode.model.db import Repository
24 from rhodecode.model.db import Repository
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
26 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
27
27
28
28
29 def route_path(name, params=None, **kwargs):
29 def route_path(name, params=None, **kwargs):
30 import urllib
30 import urllib
31
31
32 base_url = {
32 base_url = {
33 'repo_summary': '/{repo_name}',
33 'repo_summary': '/{repo_name}',
34 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
34 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
35 'edit_repo_issuetracker_test': '/{repo_name}/settings/issue_trackers/test',
35 'edit_repo_issuetracker_test': '/{repo_name}/settings/issue_trackers/test',
36 'edit_repo_issuetracker_delete': '/{repo_name}/settings/issue_trackers/delete',
36 'edit_repo_issuetracker_delete': '/{repo_name}/settings/issue_trackers/delete',
37 'edit_repo_issuetracker_update': '/{repo_name}/settings/issue_trackers/update',
37 'edit_repo_issuetracker_update': '/{repo_name}/settings/issue_trackers/update',
38 }[name].format(**kwargs)
38 }[name].format(**kwargs)
39
39
40 if params:
40 if params:
41 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
41 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 return base_url
42 return base_url
43
43
44
44
45 @pytest.mark.usefixtures("app")
45 @pytest.mark.usefixtures("app")
46 class TestRepoIssueTracker(object):
46 class TestRepoIssueTracker(object):
47 def test_issuetracker_index(self, autologin_user, backend):
47 def test_issuetracker_index(self, autologin_user, backend):
48 repo = backend.create_repo()
48 repo = backend.create_repo()
49 response = self.app.get(route_path('edit_repo_issuetracker',
49 response = self.app.get(route_path('edit_repo_issuetracker',
50 repo_name=repo.repo_name))
50 repo_name=repo.repo_name))
51 assert response.status_code == 200
51 assert response.status_code == 200
52
52
53 def test_add_and_test_issuetracker_patterns(
53 def test_add_and_test_issuetracker_patterns(
54 self, autologin_user, backend, csrf_token, request, xhr_header):
54 self, autologin_user, backend, csrf_token, request, xhr_header):
55 pattern = 'issuetracker_pat'
55 pattern = 'issuetracker_pat'
56 another_pattern = pattern+'1'
56 another_pattern = pattern+'1'
57 post_url = route_path(
57 post_url = route_path(
58 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
58 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
59 post_data = {
59 post_data = {
60 'new_pattern_pattern_0': pattern,
60 'new_pattern_pattern_0': pattern,
61 'new_pattern_url_0': 'http://url',
61 'new_pattern_url_0': 'http://url',
62 'new_pattern_prefix_0': 'prefix',
62 'new_pattern_prefix_0': 'prefix',
63 'new_pattern_description_0': 'description',
63 'new_pattern_description_0': 'description',
64 'new_pattern_pattern_1': another_pattern,
64 'new_pattern_pattern_1': another_pattern,
65 'new_pattern_url_1': '/url1',
65 'new_pattern_url_1': '/url1',
66 'new_pattern_prefix_1': 'prefix1',
66 'new_pattern_prefix_1': 'prefix1',
67 'new_pattern_description_1': 'description1',
67 'new_pattern_description_1': 'description1',
68 'csrf_token': csrf_token
68 'csrf_token': csrf_token
69 }
69 }
70 self.app.post(post_url, post_data, status=302)
70 self.app.post(post_url, post_data, status=302)
71 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
71 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
72 settings = self.settings_model.get_repo_settings()
72 settings = self.settings_model.get_repo_settings()
73 self.uid = md5(pattern)
73 self.uid = md5(pattern)
74 assert settings[self.uid]['pat'] == pattern
74 assert settings[self.uid]['pat'] == pattern
75 self.another_uid = md5(another_pattern)
75 self.another_uid = md5(another_pattern)
76 assert settings[self.another_uid]['pat'] == another_pattern
76 assert settings[self.another_uid]['pat'] == another_pattern
77
77
78 # test pattern
78 # test pattern
79 data = {'test_text': 'example of issuetracker_pat replacement',
79 data = {'test_text': 'example of issuetracker_pat replacement',
80 'csrf_token': csrf_token}
80 'csrf_token': csrf_token}
81 response = self.app.post(
81 response = self.app.post(
82 route_path('edit_repo_issuetracker_test',
82 route_path('edit_repo_issuetracker_test',
83 repo_name=backend.repo.repo_name),
83 repo_name=backend.repo.repo_name),
84 extra_environ=xhr_header, params=data)
84 extra_environ=xhr_header, params=data)
85
85
86 assert response.body == \
86 assert response.body == \
87 'example of <a class="tooltip issue-tracker-link" href="http://url" title="description">prefix</a> replacement'
87 'example of <a class="tooltip issue-tracker-link" href="http://url" title="description">prefix</a> replacement'
88
88
89 @request.addfinalizer
89 @request.addfinalizer
90 def cleanup():
90 def cleanup():
91 self.settings_model.delete_entries(self.uid)
91 self.settings_model.delete_entries(self.uid)
92 self.settings_model.delete_entries(self.another_uid)
92 self.settings_model.delete_entries(self.another_uid)
93
93
94 def test_edit_issuetracker_pattern(
94 def test_edit_issuetracker_pattern(
95 self, autologin_user, backend, csrf_token, request):
95 self, autologin_user, backend, csrf_token, request):
96 entry_key = 'issuetracker_pat_'
96 entry_key = 'issuetracker_pat_'
97 pattern = 'issuetracker_pat2'
97 pattern = 'issuetracker_pat2'
98 old_pattern = 'issuetracker_pat'
98 old_pattern = 'issuetracker_pat'
99 old_uid = md5(old_pattern)
99 old_uid = md5(old_pattern)
100
100
101 sett = SettingsModel(repo=backend.repo).create_or_update_setting(
101 sett = SettingsModel(repo=backend.repo).create_or_update_setting(
102 entry_key+old_uid, old_pattern, 'unicode')
102 entry_key+old_uid, old_pattern, 'unicode')
103 Session().add(sett)
103 Session().add(sett)
104 Session().commit()
104 Session().commit()
105 post_url = route_path(
105 post_url = route_path(
106 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
106 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
107 post_data = {
107 post_data = {
108 'new_pattern_pattern_0': pattern,
108 'new_pattern_pattern_0': pattern,
109 'new_pattern_url_0': '/url',
109 'new_pattern_url_0': '/url',
110 'new_pattern_prefix_0': 'prefix',
110 'new_pattern_prefix_0': 'prefix',
111 'new_pattern_description_0': 'description',
111 'new_pattern_description_0': 'description',
112 'uid': old_uid,
112 'uid': old_uid,
113 'csrf_token': csrf_token
113 'csrf_token': csrf_token
114 }
114 }
115 self.app.post(post_url, post_data, status=302)
115 self.app.post(post_url, post_data, status=302)
116 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
116 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
117 settings = self.settings_model.get_repo_settings()
117 settings = self.settings_model.get_repo_settings()
118 self.uid = md5(pattern)
118 self.uid = md5(pattern)
119 assert settings[self.uid]['pat'] == pattern
119 assert settings[self.uid]['pat'] == pattern
120 with pytest.raises(KeyError):
120 with pytest.raises(KeyError):
121 key = settings[old_uid]
121 key = settings[old_uid]
122
122
123 @request.addfinalizer
123 @request.addfinalizer
124 def cleanup():
124 def cleanup():
125 self.settings_model.delete_entries(self.uid)
125 self.settings_model.delete_entries(self.uid)
126
126
127 def test_delete_issuetracker_pattern(
127 def test_delete_issuetracker_pattern(
128 self, autologin_user, backend, csrf_token, settings_util):
128 self, autologin_user, backend, csrf_token, settings_util, xhr_header):
129 repo = backend.create_repo()
129 repo = backend.create_repo()
130 repo_name = repo.repo_name
130 repo_name = repo.repo_name
131 entry_key = 'issuetracker_pat_'
131 entry_key = 'issuetracker_pat_'
132 pattern = 'issuetracker_pat3'
132 pattern = 'issuetracker_pat3'
133 uid = md5(pattern)
133 uid = md5(pattern)
134 settings_util.create_repo_rhodecode_setting(
134 settings_util.create_repo_rhodecode_setting(
135 repo=backend.repo, name=entry_key+uid,
135 repo=backend.repo, name=entry_key+uid,
136 value=entry_key, type_='unicode', cleanup=False)
136 value=entry_key, type_='unicode', cleanup=False)
137
137
138 self.app.post(
138 self.app.post(
139 route_path(
139 route_path(
140 'edit_repo_issuetracker_delete',
140 'edit_repo_issuetracker_delete',
141 repo_name=backend.repo.repo_name),
141 repo_name=backend.repo.repo_name),
142 {
142 {
143 'uid': uid,
143 'uid': uid,
144 'csrf_token': csrf_token
144 'csrf_token': csrf_token,
145 }, status=302)
145 '': ''
146 }, extra_environ=xhr_header, status=200)
146 settings = IssueTrackerSettingsModel(
147 settings = IssueTrackerSettingsModel(
147 repo=Repository.get_by_repo_name(repo_name)).get_repo_settings()
148 repo=Repository.get_by_repo_name(repo_name)).get_repo_settings()
148 assert 'rhodecode_%s%s' % (entry_key, uid) not in settings
149 assert 'rhodecode_%s%s' % (entry_key, uid) not in settings
@@ -1,137 +1,139 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2019 RhodeCode GmbH
3 # Copyright (C) 2017-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 import formencode
25 import formencode
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
32 from rhodecode.model.forms import IssueTrackerPatternsForm
32 from rhodecode.model.forms import IssueTrackerPatternsForm
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34 from rhodecode.model.settings import IssueTrackerSettingsModel
34 from rhodecode.model.settings import IssueTrackerSettingsModel, SettingsModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class RepoSettingsIssueTrackersView(RepoAppView):
39 class RepoSettingsIssueTrackersView(RepoAppView):
40 def load_default_context(self):
40 def load_default_context(self):
41 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
42
42
43
43
44 return c
44 return c
45
45
46 @LoginRequired()
46 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.admin')
47 @HasRepoPermissionAnyDecorator('repository.admin')
48 @view_config(
48 @view_config(
49 route_name='edit_repo_issuetracker', request_method='GET',
49 route_name='edit_repo_issuetracker', request_method='GET',
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
51 def repo_issuetracker(self):
51 def repo_issuetracker(self):
52 c = self.load_default_context()
52 c = self.load_default_context()
53 c.active = 'issuetracker'
53 c.active = 'issuetracker'
54 c.data = 'data'
54 c.data = 'data'
55
55
56 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
56 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
57 c.global_patterns = c.settings_model.get_global_settings()
57 c.global_patterns = c.settings_model.get_global_settings()
58 c.repo_patterns = c.settings_model.get_repo_settings()
58 c.repo_patterns = c.settings_model.get_repo_settings()
59
59
60 return self._get_template_context(c)
60 return self._get_template_context(c)
61
61
62 @LoginRequired()
62 @LoginRequired()
63 @HasRepoPermissionAnyDecorator('repository.admin')
63 @HasRepoPermissionAnyDecorator('repository.admin')
64 @CSRFRequired()
64 @CSRFRequired()
65 @view_config(
65 @view_config(
66 route_name='edit_repo_issuetracker_test', request_method='POST',
66 route_name='edit_repo_issuetracker_test', request_method='POST',
67 xhr=True, renderer='string')
67 renderer='string', xhr=True)
68 def repo_issuetracker_test(self):
68 def repo_issuetracker_test(self):
69 return h.urlify_commit_message(
69 return h.urlify_commit_message(
70 self.request.POST.get('test_text', ''),
70 self.request.POST.get('test_text', ''),
71 self.db_repo_name)
71 self.db_repo_name)
72
72
73 @LoginRequired()
73 @LoginRequired()
74 @HasRepoPermissionAnyDecorator('repository.admin')
74 @HasRepoPermissionAnyDecorator('repository.admin')
75 @CSRFRequired()
75 @CSRFRequired()
76 @view_config(
76 @view_config(
77 route_name='edit_repo_issuetracker_delete', request_method='POST',
77 route_name='edit_repo_issuetracker_delete', request_method='POST',
78 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
78 renderer='json_ext', xhr=True)
79 def repo_issuetracker_delete(self):
79 def repo_issuetracker_delete(self):
80 _ = self.request.translate
80 _ = self.request.translate
81 uid = self.request.POST.get('uid')
81 uid = self.request.POST.get('uid')
82 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
82 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
83 try:
83 try:
84 repo_settings.delete_entries(uid)
84 repo_settings.delete_entries(uid)
85 except Exception:
85 except Exception:
86 h.flash(_('Error occurred during deleting issue tracker entry'),
86 h.flash(_('Error occurred during deleting issue tracker entry'),
87 category='error')
87 category='error')
88 else:
88 raise HTTPNotFound()
89 h.flash(_('Removed issue tracker entry'), category='success')
89
90 raise HTTPFound(
90 SettingsModel().invalidate_settings_cache()
91 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
91 h.flash(_('Removed issue tracker entry.'), category='success')
92
93 return {'deleted': uid}
92
94
93 def _update_patterns(self, form, repo_settings):
95 def _update_patterns(self, form, repo_settings):
94 for uid in form['delete_patterns']:
96 for uid in form['delete_patterns']:
95 repo_settings.delete_entries(uid)
97 repo_settings.delete_entries(uid)
96
98
97 for pattern_data in form['patterns']:
99 for pattern_data in form['patterns']:
98 for setting_key, pattern, type_ in pattern_data:
100 for setting_key, pattern, type_ in pattern_data:
99 sett = repo_settings.create_or_update_setting(
101 sett = repo_settings.create_or_update_setting(
100 setting_key, pattern.strip(), type_)
102 setting_key, pattern.strip(), type_)
101 Session().add(sett)
103 Session().add(sett)
102
104
103 Session().commit()
105 Session().commit()
104
106
105 @LoginRequired()
107 @LoginRequired()
106 @HasRepoPermissionAnyDecorator('repository.admin')
108 @HasRepoPermissionAnyDecorator('repository.admin')
107 @CSRFRequired()
109 @CSRFRequired()
108 @view_config(
110 @view_config(
109 route_name='edit_repo_issuetracker_update', request_method='POST',
111 route_name='edit_repo_issuetracker_update', request_method='POST',
110 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
111 def repo_issuetracker_update(self):
113 def repo_issuetracker_update(self):
112 _ = self.request.translate
114 _ = self.request.translate
113 # Save inheritance
115 # Save inheritance
114 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
116 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
115 inherited = (
117 inherited = (
116 self.request.POST.get('inherit_global_issuetracker') == "inherited")
118 self.request.POST.get('inherit_global_issuetracker') == "inherited")
117 repo_settings.inherit_global_settings = inherited
119 repo_settings.inherit_global_settings = inherited
118 Session().commit()
120 Session().commit()
119
121
120 try:
122 try:
121 form = IssueTrackerPatternsForm(self.request.translate)().to_python(self.request.POST)
123 form = IssueTrackerPatternsForm(self.request.translate)().to_python(self.request.POST)
122 except formencode.Invalid as errors:
124 except formencode.Invalid as errors:
123 log.exception('Failed to add new pattern')
125 log.exception('Failed to add new pattern')
124 error = errors
126 error = errors
125 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
127 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
126 category='error')
128 category='error')
127 raise HTTPFound(
129 raise HTTPFound(
128 h.route_path('edit_repo_issuetracker',
130 h.route_path('edit_repo_issuetracker',
129 repo_name=self.db_repo_name))
131 repo_name=self.db_repo_name))
130
132
131 if form:
133 if form:
132 self._update_patterns(form, repo_settings)
134 self._update_patterns(form, repo_settings)
133
135
134 h.flash(_('Updated issue tracker entries'), category='success')
136 h.flash(_('Updated issue tracker entries'), category='success')
135 raise HTTPFound(
137 raise HTTPFound(
136 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
138 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
137
139
@@ -1,524 +1,542 b''
1
1
2 // tables.less
2 // tables.less
3 // For use in RhodeCode application tables;
3 // For use in RhodeCode application tables;
4 // see style guide documentation for guidelines.
4 // see style guide documentation for guidelines.
5
5
6 // TABLES
6 // TABLES
7
7
8 .rctable,
8 .rctable,
9 table.rctable,
9 table.rctable,
10 table.dataTable {
10 table.dataTable {
11 clear:both;
11 clear:both;
12 width: 100%;
12 width: 100%;
13 margin: 0 auto @padding;
13 margin: 0 auto @padding;
14 padding: 0;
14 padding: 0;
15 vertical-align: baseline;
15 vertical-align: baseline;
16 line-height:1.5em;
16 line-height:1.5em;
17 border: none;
17 border: none;
18 outline: none;
18 outline: none;
19 border-collapse: collapse;
19 border-collapse: collapse;
20 border-spacing: 0;
20 border-spacing: 0;
21 color: @grey2;
21 color: @grey2;
22
22
23 b {
23 b {
24 font-weight: normal;
24 font-weight: normal;
25 }
25 }
26
26
27 em {
27 em {
28 font-weight: bold;
28 font-weight: bold;
29 font-style: normal;
29 font-style: normal;
30 }
30 }
31
31
32 .td-user {
32 .td-user {
33 .rc-user {
33 .rc-user {
34 white-space: nowrap;
34 white-space: nowrap;
35 }
35 }
36 }
36 }
37
37
38 .td-email {
38 .td-email {
39 white-space: nowrap;
39 white-space: nowrap;
40 }
40 }
41
41
42 th,
42 th,
43 td {
43 td {
44 height: auto;
44 height: auto;
45 max-width: 20%;
45 max-width: 20%;
46 padding: .65em 0 .65em 1em;
46 padding: .65em 0 .65em 1em;
47 vertical-align: middle;
47 vertical-align: middle;
48 border-bottom: @border-thickness solid @grey5;
48 border-bottom: @border-thickness solid @grey5;
49 white-space: normal;
49 white-space: normal;
50
50
51 &.td-radio,
51 &.td-radio,
52 &.td-checkbox {
52 &.td-checkbox {
53 padding-right: 0;
53 padding-right: 0;
54 text-align: center;
54 text-align: center;
55
55
56 input {
56 input {
57 margin: 0 1em;
57 margin: 0 1em;
58 }
58 }
59 }
59 }
60
60
61 &.truncate-wrap {
61 &.truncate-wrap {
62 white-space: nowrap !important;
62 white-space: nowrap !important;
63 }
63 }
64
64
65 pre {
65 pre {
66 margin: 0;
66 margin: 0;
67 }
67 }
68
68
69 .show_more {
69 .show_more {
70 height: inherit;
70 height: inherit;
71 }
71 }
72 }
72 }
73
73
74 .expired td {
74 .expired td {
75 background-color: @grey7;
75 background-color: @grey7;
76 }
76 }
77 .inactive td {
77 .inactive td {
78 background-color: @grey6;
78 background-color: @grey6;
79 }
79 }
80 th {
80 th {
81 text-align: left;
81 text-align: left;
82 font-weight: @text-semibold-weight;
82 font-weight: @text-semibold-weight;
83 font-family: @text-semibold;
83 font-family: @text-semibold;
84 }
84 }
85
85
86 .hl {
86 .hl {
87 td {
87 td {
88 background-color: lighten(@alert4,25%);
88 background-color: lighten(@alert4,25%);
89 }
89 }
90 }
90 }
91
91
92 // Special Data Cell Types
92 // Special Data Cell Types
93 // See style guide for desciptions and examples.
93 // See style guide for desciptions and examples.
94
94
95 td {
95 td {
96
96
97 &.user {
97 &.user {
98 padding-left: 1em;
98 padding-left: 1em;
99 }
99 }
100
100
101 &.td-rss {
101 &.td-rss {
102 width: 20px;
102 width: 20px;
103 min-width: 0;
103 min-width: 0;
104 margin: 0;
104 margin: 0;
105 }
105 }
106
106
107 &.quick_repo_menu {
107 &.quick_repo_menu {
108 width: 15px;
108 width: 15px;
109 text-align: center;
109 text-align: center;
110
110
111 &:hover {
111 &:hover {
112 background-color: @grey5;
112 background-color: @grey5;
113 }
113 }
114 }
114 }
115
115
116 &.td-icon {
116 &.td-icon {
117 min-width: 20px;
117 min-width: 20px;
118 width: 20px;
118 width: 20px;
119 }
119 }
120
120
121 &.td-hash {
121 &.td-hash {
122 min-width: 80px;
122 min-width: 80px;
123 width: 200px;
123 width: 200px;
124
124
125 .obsolete {
125 .obsolete {
126 text-decoration: line-through;
126 text-decoration: line-through;
127 color: lighten(@grey2,25%);
127 color: lighten(@grey2,25%);
128 }
128 }
129 }
129 }
130
130
131 &.td-sha {
131 &.td-sha {
132 white-space: nowrap;
132 white-space: nowrap;
133 }
133 }
134
134
135 &.td-graphbox {
135 &.td-graphbox {
136 width: 100px;
136 width: 100px;
137 max-width: 100px;
137 max-width: 100px;
138 min-width: 100px;
138 min-width: 100px;
139 }
139 }
140
140
141 &.td-time {
141 &.td-time {
142 width: 160px;
142 width: 160px;
143 white-space: nowrap;
143 white-space: nowrap;
144 }
144 }
145
145
146 &.annotate{
146 &.annotate{
147 padding-right: 0;
147 padding-right: 0;
148
148
149 div.annotatediv{
149 div.annotatediv{
150 margin: 0 0.7em;
150 margin: 0 0.7em;
151 }
151 }
152 }
152 }
153
153
154 &.tags-col {
154 &.tags-col {
155 padding-right: 0;
155 padding-right: 0;
156 }
156 }
157
157
158 &.td-description {
158 &.td-description {
159 min-width: 350px;
159 min-width: 350px;
160
160
161 &.truncate, .truncate-wrap {
161 &.truncate, .truncate-wrap {
162 white-space: nowrap;
162 white-space: nowrap;
163 overflow: hidden;
163 overflow: hidden;
164 text-overflow: ellipsis;
164 text-overflow: ellipsis;
165 max-width: 350px;
165 max-width: 350px;
166 }
166 }
167 }
167 }
168
168
169 &.td-grid-name {
169 &.td-grid-name {
170 white-space: nowrap;
170 white-space: nowrap;
171 min-width: 300px;
171 min-width: 300px;
172 }
172 }
173
173
174 &.td-componentname {
174 &.td-componentname {
175 white-space: nowrap;
175 white-space: nowrap;
176 }
176 }
177
177
178 &.td-name {
178 &.td-name {
179
179
180 }
180 }
181
181
182 &.td-journalaction {
182 &.td-journalaction {
183 min-width: 300px;
183 min-width: 300px;
184
184
185 .journal_action_params {
185 .journal_action_params {
186 // waiting for feedback
186 // waiting for feedback
187 }
187 }
188 }
188 }
189
189
190 &.td-active {
190 &.td-active {
191 padding-left: .65em;
191 padding-left: .65em;
192 }
192 }
193
193
194 &.td-issue-tracker-name {
195 width: 180px;
196 input {
197 width: 180px;
198 }
199
200 }
201
202 &.td-issue-tracker-regex {
203 white-space: nowrap;
204
205 min-width: 300px;
206 input {
207 min-width: 300px;
208 }
209
210 }
211
194 &.td-url {
212 &.td-url {
195 white-space: nowrap;
213 white-space: nowrap;
196 }
214 }
197
215
198 &.td-comments {
216 &.td-comments {
199 min-width: 3em;
217 min-width: 3em;
200 }
218 }
201
219
202 &.td-buttons {
220 &.td-buttons {
203 padding: .3em 0;
221 padding: .3em 0;
204 }
222 }
205 &.td-align-top {
223 &.td-align-top {
206 vertical-align: text-top
224 vertical-align: text-top
207 }
225 }
208 &.td-action {
226 &.td-action {
209 // this is for the remove/delete/edit buttons
227 // this is for the remove/delete/edit buttons
210 padding-right: 0;
228 padding-right: 0;
211 min-width: 95px;
229 min-width: 95px;
212 text-transform: capitalize;
230 text-transform: capitalize;
213
231
214 i {
232 i {
215 display: none;
233 display: none;
216 }
234 }
217 }
235 }
218
236
219 // TODO: lisa: this needs to be cleaned up with the buttons
237 // TODO: lisa: this needs to be cleaned up with the buttons
220 .grid_edit,
238 .grid_edit,
221 .grid_delete {
239 .grid_delete {
222 display: inline-block;
240 display: inline-block;
223 margin: 0 @padding/3 0 0;
241 margin: 0 @padding/3 0 0;
224 font-family: @text-light;
242 font-family: @text-light;
225
243
226 i {
244 i {
227 display: none;
245 display: none;
228 }
246 }
229 }
247 }
230
248
231 .grid_edit + .grid_delete {
249 .grid_edit + .grid_delete {
232 border-left: @border-thickness solid @grey5;
250 border-left: @border-thickness solid @grey5;
233 padding-left: @padding/2;
251 padding-left: @padding/2;
234 }
252 }
235
253
236 &.td-compare {
254 &.td-compare {
237
255
238 input {
256 input {
239 margin-right: 1em;
257 margin-right: 1em;
240 }
258 }
241
259
242 .compare-radio-button {
260 .compare-radio-button {
243 margin: 0 1em 0 0;
261 margin: 0 1em 0 0;
244 }
262 }
245
263
246
264
247 }
265 }
248
266
249 &.td-tags {
267 &.td-tags {
250 padding: .5em 1em .5em 0;
268 padding: .5em 1em .5em 0;
251 width: 140px;
269 width: 140px;
252
270
253 .tag {
271 .tag {
254 margin: 1px;
272 margin: 1px;
255 float: left;
273 float: left;
256 }
274 }
257 }
275 }
258
276
259 .icon-svn, .icon-hg, .icon-git {
277 .icon-svn, .icon-hg, .icon-git {
260 font-size: 1.4em;
278 font-size: 1.4em;
261 }
279 }
262
280
263 &.collapse_commit,
281 &.collapse_commit,
264 &.expand_commit {
282 &.expand_commit {
265 padding-right: 0;
283 padding-right: 0;
266 padding-left: 1em;
284 padding-left: 1em;
267 cursor: pointer;
285 cursor: pointer;
268 width: 20px;
286 width: 20px;
269 }
287 }
270 }
288 }
271
289
272 .perm_admin_row {
290 .perm_admin_row {
273 color: @grey4;
291 color: @grey4;
274 background-color: @grey6;
292 background-color: @grey6;
275 }
293 }
276
294
277 .noborder {
295 .noborder {
278 border: none;
296 border: none;
279
297
280 td {
298 td {
281 border: none;
299 border: none;
282 }
300 }
283 }
301 }
284 }
302 }
285 .rctable.audit-log {
303 .rctable.audit-log {
286 td {
304 td {
287 vertical-align: top;
305 vertical-align: top;
288 }
306 }
289 }
307 }
290
308
291 // TRUNCATING
309 // TRUNCATING
292 // TODO: lisaq: should this possibly be moved out of tables.less?
310 // TODO: lisaq: should this possibly be moved out of tables.less?
293 // for truncated text
311 // for truncated text
294 // used inside of table cells and in code block headers
312 // used inside of table cells and in code block headers
295 .truncate-wrap {
313 .truncate-wrap {
296 white-space: nowrap !important;
314 white-space: nowrap !important;
297
315
298 //truncated text
316 //truncated text
299 .truncate {
317 .truncate {
300 max-width: 450px;
318 max-width: 450px;
301 width: 300px;
319 width: 300px;
302 overflow: hidden;
320 overflow: hidden;
303 text-overflow: ellipsis;
321 text-overflow: ellipsis;
304 -o-text-overflow: ellipsis;
322 -o-text-overflow: ellipsis;
305 -ms-text-overflow: ellipsis;
323 -ms-text-overflow: ellipsis;
306
324
307 &.autoexpand {
325 &.autoexpand {
308 width: 120px;
326 width: 120px;
309 margin-right: 200px;
327 margin-right: 200px;
310 }
328 }
311 }
329 }
312 &:hover .truncate.autoexpand {
330 &:hover .truncate.autoexpand {
313 overflow: visible;
331 overflow: visible;
314 }
332 }
315
333
316 .tags-truncate {
334 .tags-truncate {
317 width: 150px;
335 width: 150px;
318 height: 22px;
336 height: 22px;
319 overflow: hidden;
337 overflow: hidden;
320
338
321 .tag {
339 .tag {
322 display: inline-block;
340 display: inline-block;
323 }
341 }
324
342
325 &.truncate {
343 &.truncate {
326 height: 22px;
344 height: 22px;
327 max-height:2em;
345 max-height:2em;
328 width: 140px;
346 width: 140px;
329 }
347 }
330 }
348 }
331 }
349 }
332
350
333 .apikeys_wrap {
351 .apikeys_wrap {
334 margin-bottom: @padding;
352 margin-bottom: @padding;
335
353
336 table.rctable td:first-child {
354 table.rctable td:first-child {
337 width: 340px;
355 width: 340px;
338 }
356 }
339 }
357 }
340
358
341
359
342
360
343 // SPECIAL CASES
361 // SPECIAL CASES
344
362
345 // Repository Followers
363 // Repository Followers
346 table.rctable.followers_data {
364 table.rctable.followers_data {
347 width: 75%;
365 width: 75%;
348 margin: 0;
366 margin: 0;
349 }
367 }
350
368
351 // Repository List
369 // Repository List
352 // Group Members List
370 // Group Members List
353 table.rctable.group_members,
371 table.rctable.group_members,
354 table#repo_list_table {
372 table#repo_list_table {
355 min-width: 600px;
373 min-width: 600px;
356 }
374 }
357
375
358 // Keyboard mappings
376 // Keyboard mappings
359 table.keyboard-mappings {
377 table.keyboard-mappings {
360 th {
378 th {
361 text-align: left;
379 text-align: left;
362 font-weight: @text-semibold-weight;
380 font-weight: @text-semibold-weight;
363 font-family: @text-semibold;
381 font-family: @text-semibold;
364 }
382 }
365 }
383 }
366
384
367 // Branches, Tags, and Bookmarks
385 // Branches, Tags, and Bookmarks
368 #obj_list_table.dataTable {
386 #obj_list_table.dataTable {
369 td.td-time {
387 td.td-time {
370 padding-right: 1em;
388 padding-right: 1em;
371 }
389 }
372 }
390 }
373
391
374 // User Admin
392 // User Admin
375 .rctable.useremails,
393 .rctable.useremails,
376 .rctable.account_emails {
394 .rctable.account_emails {
377 .tag,
395 .tag,
378 .btn {
396 .btn {
379 float: right;
397 float: right;
380 }
398 }
381 .btn { //to line up with tags
399 .btn { //to line up with tags
382 margin-right: 1.65em;
400 margin-right: 1.65em;
383 }
401 }
384 }
402 }
385
403
386 // User List
404 // User List
387 #user_list_table {
405 #user_list_table {
388
406
389 td.td-user {
407 td.td-user {
390 min-width: 100px;
408 min-width: 100px;
391 }
409 }
392 }
410 }
393
411
394 // Pull Request List Table
412 // Pull Request List Table
395 #pull_request_list_table.dataTable {
413 #pull_request_list_table.dataTable {
396
414
397 //TODO: lisa: This needs to be removed once the description is adjusted
415 //TODO: lisa: This needs to be removed once the description is adjusted
398 // for using an expand_commit button (see issue 765)
416 // for using an expand_commit button (see issue 765)
399 td {
417 td {
400 vertical-align: middle;
418 vertical-align: middle;
401 }
419 }
402 }
420 }
403
421
404 // Settings (no border)
422 // Settings (no border)
405 table.rctable.dl-settings {
423 table.rctable.dl-settings {
406 td {
424 td {
407 border: none;
425 border: none;
408 vertical-align: baseline;
426 vertical-align: baseline;
409 }
427 }
410 }
428 }
411
429
412
430
413 // Statistics
431 // Statistics
414 table.trending_language_tbl {
432 table.trending_language_tbl {
415 width: 100%;
433 width: 100%;
416 line-height: 1em;
434 line-height: 1em;
417
435
418 td div {
436 td div {
419 overflow: visible;
437 overflow: visible;
420 }
438 }
421 }
439 }
422
440
423 .trending_language_tbl, .trending_language_tbl td {
441 .trending_language_tbl, .trending_language_tbl td {
424 border: 0;
442 border: 0;
425 margin: 0;
443 margin: 0;
426 padding: 0;
444 padding: 0;
427 background: transparent;
445 background: transparent;
428 }
446 }
429
447
430 .trending_language_tbl, .trending_language_tbl tr {
448 .trending_language_tbl, .trending_language_tbl tr {
431 border-spacing: 0 3px;
449 border-spacing: 0 3px;
432 }
450 }
433
451
434 .trending_language {
452 .trending_language {
435 position: relative;
453 position: relative;
436 overflow: hidden;
454 overflow: hidden;
437 color: @text-color;
455 color: @text-color;
438 width: 400px;
456 width: 400px;
439
457
440 .lang-bar {
458 .lang-bar {
441 z-index: 1;
459 z-index: 1;
442 overflow: hidden;
460 overflow: hidden;
443 background-color: @rcblue;
461 background-color: @rcblue;
444 color: #FFF;
462 color: #FFF;
445 text-decoration: none;
463 text-decoration: none;
446 }
464 }
447
465
448 }
466 }
449
467
450 // Changesets
468 // Changesets
451 #changesets.rctable {
469 #changesets.rctable {
452 th {
470 th {
453 padding: 0 1em 0.65em 0;
471 padding: 0 1em 0.65em 0;
454 }
472 }
455
473
456 // td must be fixed height for graph
474 // td must be fixed height for graph
457 td {
475 td {
458 height: 32px;
476 height: 32px;
459 padding: 0 1em 0 0;
477 padding: 0 1em 0 0;
460 vertical-align: middle;
478 vertical-align: middle;
461 white-space: nowrap;
479 white-space: nowrap;
462
480
463 &.td-description {
481 &.td-description {
464 white-space: normal;
482 white-space: normal;
465 }
483 }
466
484
467 &.expand_commit {
485 &.expand_commit {
468 padding-right: 0;
486 padding-right: 0;
469 cursor: pointer;
487 cursor: pointer;
470 width: 20px;
488 width: 20px;
471 }
489 }
472 }
490 }
473 }
491 }
474
492
475 // Compare
493 // Compare
476 table.compare_view_commits {
494 table.compare_view_commits {
477 margin-top: @space;
495 margin-top: @space;
478
496
479 td.td-time {
497 td.td-time {
480 padding-left: .5em;
498 padding-left: .5em;
481 }
499 }
482
500
483 // special case to not show hover actions on hidden indicator
501 // special case to not show hover actions on hidden indicator
484 tr.compare_select_hidden:hover {
502 tr.compare_select_hidden:hover {
485 cursor: inherit;
503 cursor: inherit;
486
504
487 td {
505 td {
488 background-color: inherit;
506 background-color: inherit;
489 }
507 }
490 }
508 }
491
509
492 tr:hover {
510 tr:hover {
493 cursor: pointer;
511 cursor: pointer;
494
512
495 td {
513 td {
496 background-color: lighten(@alert4,25%);
514 background-color: lighten(@alert4,25%);
497 }
515 }
498 }
516 }
499
517
500
518
501 }
519 }
502
520
503 .file_history {
521 .file_history {
504 td.td-actions {
522 td.td-actions {
505 text-align: right;
523 text-align: right;
506 }
524 }
507 }
525 }
508
526
509
527
510 // Gist List
528 // Gist List
511 #gist_list_table {
529 #gist_list_table {
512 td {
530 td {
513 vertical-align: middle;
531 vertical-align: middle;
514
532
515 div{
533 div{
516 display: inline-block;
534 display: inline-block;
517 vertical-align: middle;
535 vertical-align: middle;
518 }
536 }
519
537
520 img{
538 img{
521 vertical-align: middle;
539 vertical-align: middle;
522 }
540 }
523 }
541 }
524 }
542 }
@@ -1,221 +1,321 b''
1 ## snippet for displaying issue tracker settings
1 ## snippet for displaying issue tracker settings
2 ## usage:
2 ## usage:
3 ## <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
3 ## <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
4 ## ${its.issue_tracker_settings_table(patterns, form_url, delete_url)}
4 ## ${its.issue_tracker_settings_table(patterns, form_url, delete_url)}
5 ## ${its.issue_tracker_settings_test(test_url)}
5 ## ${its.issue_tracker_settings_test(test_url)}
6
6
7 <%def name="issue_tracker_settings_table(patterns, form_url, delete_url)">
7 <%def name="issue_tracker_settings_table(patterns, form_url, delete_url)">
8 <%
9 # Name/desc, pattern, issue prefix
10 examples = [
11 (
12 ' ',
13 ' ',
14 ' ',
15 ' '
16 ),
17
18 (
19 'Redmine',
20 '(^#|\s#)(?P<issue_id>\d+)',
21 'https://myissueserver.com/${repo}/issue/${issue_id}',
22 ''
23 ),
24
25 (
26 'Redmine - Alternative',
27 '(?:issue-)(\d+)',
28 'https://myissueserver.com/redmine/issue/${id}',
29 ''
30 ),
31
32 (
33 'Redmine - Wiki',
34 '(?:wiki-)([a-zA-Z0-9]+)',
35 'http://example.org/projects/${repo_name}/wiki/${id}',
36 'wiki-'
37 ),
38
39 (
40 'JIRA - All tickets',
41 '(^|\s\w+-\d+)',
42 'https://myjira.com/browse/${id}',
43 ''
44 ),
45
46 (
47 'JIRA - Project (JRA)',
48 '(?:(^|\s)(?P<issue_id>(?:JRA-|JRA-)(?:\d+)))',
49 'https://myjira.com/${issue_id}',
50 ''
51 ),
52
53 (
54 'Confluence WIKI',
55 '(?:conf-)([A-Z0-9]+)',
56 'https://example.atlassian.net/display/wiki/${id}/${repo_name}',
57 'CONF-',
58 ),
59
60 (
61 'Pivotal Tracker',
62 '(?:pivot-)(?<project_id>\d+)-(?<story>\d+)',
63 'https://www.pivotaltracker.com/s/projects/${project_id}/stories/${story}',
64 'PIV-',
65 ),
66
67 (
68 'Trello',
69 '(?:trello-)(?<card_id>[a-zA-Z0-9]+)',
70 'https://trello.com/example.com/${card_id}',
71 'TRELLO-',
72 ),
73 ]
74 %>
75
8 <table class="rctable issuetracker">
76 <table class="rctable issuetracker">
9 <tr>
77 <tr>
10 <th>${_('Description')}</th>
78 <th>${_('Description')}</th>
11 <th>${_('Pattern')}</th>
79 <th>${_('Pattern')}</th>
12 <th>${_('Url')}</th>
80 <th>${_('Url')}</th>
13 <th>${_('Prefix')}</th>
81 <th>${_('Extra Prefix')}</th>
14 <th ></th>
82 <th ></th>
15 </tr>
83 </tr>
16 <tr>
84 % for name, pat, url, pref in examples:
17 <td class="td-description issue-tracker-example">Example</td>
85 <tr class="it-examples" style="${'' if loop.index == 0 else 'display:none'}">
18 <td class="td-regex issue-tracker-example">${'(?:#)(?P<issue_id>\d+)'}</td>
86 <td class="td-issue-tracker-name issue-tracker-example">${name}</td>
19 <td class="td-url issue-tracker-example">${'https://myissueserver.com/${repo}/issue/${issue_id}'}</td>
87 <td class="td-regex issue-tracker-example">${pat}</td>
20 <td class="td-prefix issue-tracker-example">#</td>
88 <td class="td-url issue-tracker-example">${url}</td>
21 <td class="issue-tracker-example"><a href="${h.route_url('enterprise_issue_tracker_settings')}" target="_blank">${_('Read more')}</a></td>
89 <td class="td-prefix issue-tracker-example">${pref}</td>
22 </tr>
90 <td>
91 % if loop.index == 0:
92 <a href="#showMore" onclick="$('.it-examples').toggle(); return false">${_('show examples')}</a>
93 % else:
94 <a href="#copyToInput" onclick="copyToInput(this, '${h.json.dumps(name)}', '${h.json.dumps(pat)}', '${h.json.dumps(url)}', '${h.json.dumps(pref)}'); return false">copy to input</a>
95 % endif
96 </td>
97 </tr>
98 % endfor
99
23 %for uid, entry in patterns:
100 %for uid, entry in patterns:
24 <tr id="entry_${uid}">
101 <tr id="entry_${uid}">
25 <td class="td-description issuetracker_desc">
102 <td class="td-issue-tracker-name issuetracker_desc">
26 <span class="entry">
103 <span class="entry">
27 ${entry.desc}
104 ${entry.desc}
28 </span>
105 </span>
29 <span class="edit">
106 <span class="edit">
30 ${h.text('new_pattern_description_'+uid, class_='medium-inline', value=entry.desc or '')}
107 ${h.text('new_pattern_description_'+uid, class_='medium-inline', value=entry.desc or '')}
31 </span>
108 </span>
32 </td>
109 </td>
33 <td class="td-regex issuetracker_pat">
110 <td class="td-issue-tracker-regex issuetracker_pat">
34 <span class="entry">
111 <span class="entry">
35 ${entry.pat}
112 ${entry.pat}
36 </span>
113 </span>
37 <span class="edit">
114 <span class="edit">
38 ${h.text('new_pattern_pattern_'+uid, class_='medium-inline', value=entry.pat or '')}
115 ${h.text('new_pattern_pattern_'+uid, class_='medium-inline', value=entry.pat or '')}
39 </span>
116 </span>
40 </td>
117 </td>
41 <td class="td-url issuetracker_url">
118 <td class="td-url issuetracker_url">
42 <span class="entry">
119 <span class="entry">
43 ${entry.url}
120 ${entry.url}
44 </span>
121 </span>
45 <span class="edit">
122 <span class="edit">
46 ${h.text('new_pattern_url_'+uid, class_='medium-inline', value=entry.url or '')}
123 ${h.text('new_pattern_url_'+uid, class_='medium-inline', value=entry.url or '')}
47 </span>
124 </span>
48 </td>
125 </td>
49 <td class="td-prefix issuetracker_pref">
126 <td class="td-prefix issuetracker_pref">
50 <span class="entry">
127 <span class="entry">
51 ${entry.pref}
128 ${entry.pref}
52 </span>
129 </span>
53 <span class="edit">
130 <span class="edit">
54 ${h.text('new_pattern_prefix_'+uid, class_='medium-inline', value=entry.pref or '')}
131 ${h.text('new_pattern_prefix_'+uid, class_='medium-inline', value=entry.pref or '')}
55 </span>
132 </span>
56 </td>
133 </td>
57 <td class="td-action">
134 <td class="td-action">
58 <div class="grid_edit">
135 <div class="grid_edit">
59 <span class="entry">
136 <span class="entry">
60 <a class="edit_issuetracker_entry" href="">${_('Edit')}</a>
137 <a class="edit_issuetracker_entry" href="">${_('Edit')}</a>
61 </span>
138 </span>
62 <span class="edit">
139 <span class="edit">
63 <input id="uid_${uid}" name="uid" type="hidden" value="${uid}">
140 <input id="uid_${uid}" name="uid" type="hidden" value="${uid}">
64 </span>
141 </span>
65 </div>
142 </div>
66 <div class="grid_delete">
143 <div class="grid_delete">
67 <span class="entry">
144 <span class="entry">
68 <a class="btn btn-link btn-danger delete_issuetracker_entry" data-desc="${entry.desc}" data-uid="${uid}">
145 <a class="btn btn-link btn-danger delete_issuetracker_entry" data-desc="${entry.desc}" data-uid="${uid}">
69 ${_('Delete')}
146 ${_('Delete')}
70 </a>
147 </a>
71 </span>
148 </span>
72 <span class="edit">
149 <span class="edit">
73 <a class="btn btn-link btn-danger edit_issuetracker_cancel" data-uid="${uid}">${_('Cancel')}</a>
150 <a class="btn btn-link btn-danger edit_issuetracker_cancel" data-uid="${uid}">${_('Cancel')}</a>
74 </span>
151 </span>
75 </div>
152 </div>
76 </td>
153 </td>
77 </tr>
154 </tr>
78 %endfor
155 %endfor
79 <tr id="last-row"></tr>
156 <tr id="last-row"></tr>
80 </table>
157 </table>
81 <p>
158 <p>
82 <a id="add_pattern" class="link">
159 <a id="add_pattern" class="link">
83 ${_('Add new')}
160 ${_('Add new')}
84 </a>
161 </a>
85 </p>
162 </p>
86
163
87 <script type="text/javascript">
164 <script type="text/javascript">
88 var newEntryLabel = $('label[for="new_entry"]');
165 var newEntryLabel = $('label[for="new_entry"]');
89
166
90 var resetEntry = function() {
167 var resetEntry = function() {
91 newEntryLabel.text("${_('New Entry')}:");
168 newEntryLabel.text("${_('New Entry')}:");
92 };
169 };
93
170
94 var delete_pattern = function(entry) {
171 var delete_pattern = function(entry) {
95 if (confirm("${_('Confirm to remove this pattern:')} "+$(entry).data('desc'))) {
172 if (confirm("${_('Confirm to remove this pattern:')} "+$(entry).data('desc'))) {
96 $.ajax({
173 $.ajax({
97 type: "POST",
174 type: "POST",
98 url: "${delete_url}",
175 url: "${delete_url}",
99 data: {
176 data: {
100 'csrf_token': CSRF_TOKEN,
177 'csrf_token': CSRF_TOKEN,
101 'uid':$(entry).data('uid')
178 'uid':$(entry).data('uid')
102 },
179 },
103 success: function(){
180 success: function(){
104 location.reload();
181 window.location.reload();
105 },
182 },
106 error: function(data, textStatus, errorThrown){
183 error: function(data, textStatus, errorThrown){
107 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
184 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
108 }
185 }
109 });
186 });
110 }
187 }
111 };
188 };
112
189
113 $('.delete_issuetracker_entry').on('click', function(e){
190 $('.delete_issuetracker_entry').on('click', function(e){
114 e.preventDefault();
191 e.preventDefault();
115 delete_pattern(this);
192 delete_pattern(this);
116 });
193 });
117
194
118 $('.edit_issuetracker_entry').on('click', function(e){
195 $('.edit_issuetracker_entry').on('click', function(e){
119 e.preventDefault();
196 e.preventDefault();
120 $(this).parents('tr').addClass('editopen');
197 $(this).parents('tr').addClass('editopen');
121 });
198 });
122
199
123 $('.edit_issuetracker_cancel').on('click', function(e){
200 $('.edit_issuetracker_cancel').on('click', function(e){
124 e.preventDefault();
201 e.preventDefault();
125 $(this).parents('tr').removeClass('editopen');
202 $(this).parents('tr').removeClass('editopen');
126 // Reset to original value
203 // Reset to original value
127 var uid = $(this).data('uid');
204 var uid = $(this).data('uid');
128 $('#'+uid+' input').each(function(e) {
205 $('#'+uid+' input').each(function(e) {
129 this.value = this.defaultValue;
206 this.value = this.defaultValue;
130 });
207 });
131 });
208 });
132
209
133 $('input#reset').on('click', function(e) {
210 $('input#reset').on('click', function(e) {
134 resetEntry();
211 resetEntry();
135 });
212 });
136
213
137 $('#add_pattern').on('click', function(e) {
214 $('#add_pattern').on('click', function(e) {
138 addNewPatternInput();
215 addNewPatternInput();
139 });
216 });
217
218 var copied = false;
219 copyToInput = function (elem, name, pat, url, pref) {
220 if (copied === false) {
221 addNewPatternInput();
222 copied = true;
223 }
224 $(elem).hide();
225 var load = function(text){
226 return text.replace(/["]/g, "")
227 };
228 $('#description_1').val(load(name));
229 $('#pattern_1').val(load(pat));
230 $('#url_1').val(load(url));
231 $('#prefix_1').val(load(pref));
232
233 }
234
140 </script>
235 </script>
141 </%def>
236 </%def>
142
237
143 <%def name="issue_tracker_new_row()">
238 <%def name="issue_tracker_new_row()">
144 <table id="add-row-tmpl" style="display: none;">
239 <table id="add-row-tmpl" style="display: none;">
145 <tbody>
240 <tbody>
146 <tr class="new_pattern">
241 <tr class="new_pattern">
147 <td class="td-description issuetracker_desc">
242 <td class="td-issue-tracker-name issuetracker_desc">
148 <span class="entry">
243 <span class="entry">
149 <input class="medium-inline" id="description_##UUID##" name="new_pattern_description_##UUID##" value="##DESCRIPTION##" type="text">
244 <input class="medium-inline" id="description_##UUID##" name="new_pattern_description_##UUID##" value="##DESCRIPTION##" type="text">
150 </span>
245 </span>
151 </td>
246 </td>
152 <td class="td-regex issuetracker_pat">
247 <td class="td-issue-tracker-regex issuetracker_pat">
153 <span class="entry">
248 <span class="entry">
154 <input class="medium-inline" id="pattern_##UUID##" name="new_pattern_pattern_##UUID##" placeholder="Pattern"
249 <input class="medium-inline" id="pattern_##UUID##" name="new_pattern_pattern_##UUID##" placeholder="Pattern"
155 value="##PATTERN##" type="text">
250 value="##PATTERN##" type="text">
156 </span>
251 </span>
157 </td>
252 </td>
158 <td class="td-url issuetracker_url">
253 <td class="td-url issuetracker_url">
159 <span class="entry">
254 <span class="entry">
160 <input class="medium-inline" id="url_##UUID##" name="new_pattern_url_##UUID##" placeholder="Url" value="##URL##" type="text">
255 <input class="medium-inline" id="url_##UUID##" name="new_pattern_url_##UUID##" placeholder="Url" value="##URL##" type="text">
161 </span>
256 </span>
162 </td>
257 </td>
163 <td class="td-prefix issuetracker_pref">
258 <td class="td-prefix issuetracker_pref">
164 <span class="entry">
259 <span class="entry">
165 <input class="medium-inline" id="prefix_##UUID##" name="new_pattern_prefix_##UUID##" placeholder="Prefix" value="##PREFIX##" type="text">
260 <input class="medium-inline" id="prefix_##UUID##" name="new_pattern_prefix_##UUID##" placeholder="Prefix" value="##PREFIX##" type="text">
166 </span>
261 </span>
167 </td>
262 </td>
168 <td class="td-action">
263 <td class="td-action">
169 </td>
264 </td>
170 <input id="uid_##UUID##" name="uid_##UUID##" type="hidden" value="">
265 <input id="uid_##UUID##" name="uid_##UUID##" type="hidden" value="">
171 </tr>
266 </tr>
172 </tbody>
267 </tbody>
173 </table>
268 </table>
174 </%def>
269 </%def>
175
270
176 <%def name="issue_tracker_settings_test(test_url)">
271 <%def name="issue_tracker_settings_test(test_url)">
177 <div class="form-vertical">
272 <div class="form-vertical">
178 <div class="fields">
273 <div class="fields">
179 <div class="field">
274 <div class="field">
180 <div class='textarea-full'>
275 <div class='textarea-full'>
181 <textarea id="test_pattern_data" rows="10">
276 <textarea id="test_pattern_data" rows="12">
182 This is an example text for testing issue tracker patterns.
277 This is an example text for testing issue tracker patterns.
183 This commit fixes ticket #451.
278 This commit fixes ticket #451 and ticket #910.
279 Following tickets will get mentioned:
280 #123
281 #456
282 JRA-123
283 JRA-456
184 Open a pull request !101 to contribute !
284 Open a pull request !101 to contribute !
185 Added tag v1.3.0 for commit 0f3b629be725
285 Added tag v1.3.0 for commit 0f3b629be725
186
286
187 Add a test pattern here and hit preview to see the link.
287 Add a test pattern here and hit preview to see the link.
188 </textarea>
288 </textarea>
189 </div>
289 </div>
190 </div>
290 </div>
191 </div>
291 </div>
192 <div class="test_pattern_preview">
292 <div class="test_pattern_preview">
193 <div id="test_pattern" class="btn btn-small" >${_('Preview')}</div>
293 <div id="test_pattern" class="btn btn-small" >${_('Preview')}</div>
194 <p>${_('Test Pattern Preview')}</p>
294 <p>${_('Test Pattern Preview')}</p>
195 <div id="test_pattern_result" style="white-space: pre-wrap"></div>
295 <div id="test_pattern_result" style="white-space: pre-wrap"></div>
196 </div>
296 </div>
197 </div>
297 </div>
198
298
199 <script type="text/javascript">
299 <script type="text/javascript">
200 $('#test_pattern').on('click', function(e) {
300 $('#test_pattern').on('click', function(e) {
201 $.ajax({
301 $.ajax({
202 type: "POST",
302 type: "POST",
203 url: "${test_url}",
303 url: "${test_url}",
204 data: {
304 data: {
205 'test_text': $('#test_pattern_data').val(),
305 'test_text': $('#test_pattern_data').val(),
206 'csrf_token': CSRF_TOKEN
306 'csrf_token': CSRF_TOKEN
207 },
307 },
208 success: function(data){
308 success: function(data){
209 $('#test_pattern_result').html(data);
309 $('#test_pattern_result').html(data);
210 tooltipActivate();
310 tooltipActivate();
211 },
311 },
212 error: function(jqXHR, textStatus, errorThrown){
312 error: function(jqXHR, textStatus, errorThrown){
213 $('#test_pattern_result').html('Error: ' + errorThrown);
313 $('#test_pattern_result').html('Error: ' + errorThrown);
214 }
314 }
215 });
315 });
216 $('#test_pattern_result').show();
316 $('#test_pattern_result').show();
217 });
317 });
218 </script>
318 </script>
219 </%def>
319 </%def>
220
320
221
321
General Comments 0
You need to be logged in to leave comments. Login now