Show More
@@ -21,8 +21,7 b'' | |||||
21 | import logging |
|
21 | import logging | |
22 |
|
22 | |||
23 | from rhodecode.integrations.registry import IntegrationTypeRegistry |
|
23 | from rhodecode.integrations.registry import IntegrationTypeRegistry | |
24 | from rhodecode.integrations.types import webhook, slack, hipchat, email |
|
24 | from rhodecode.integrations.types import webhook, slack, hipchat, email, base | |
25 |
|
||||
26 | log = logging.getLogger(__name__) |
|
25 | log = logging.getLogger(__name__) | |
27 |
|
26 | |||
28 |
|
27 | |||
@@ -41,6 +40,13 b' integration_type_registry.register_integ' | |||||
41 | email.EmailIntegrationType) |
|
40 | email.EmailIntegrationType) | |
42 |
|
41 | |||
43 |
|
42 | |||
|
43 | # dummy EE integration to show users what we have in EE edition | |||
|
44 | integration_type_registry.register_integration_type( | |||
|
45 | base.EEIntegration('Jira Issues integration', 'jira')) | |||
|
46 | integration_type_registry.register_integration_type( | |||
|
47 | base.EEIntegration('Redmine Tracker integration', 'redmine')) | |||
|
48 | ||||
|
49 | ||||
44 | def integrations_event_handler(event): |
|
50 | def integrations_event_handler(event): | |
45 | """ |
|
51 | """ | |
46 | Takes an event and passes it to all enabled integrations |
|
52 | Takes an event and passes it to all enabled integrations |
@@ -18,11 +18,12 b'' | |||||
18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
19 |
|
19 | |||
20 | import logging |
|
20 | import logging | |
|
21 | import collections | |||
21 |
|
22 | |||
22 | log = logging.getLogger(__name__) |
|
23 | log = logging.getLogger(__name__) | |
23 |
|
24 | |||
24 |
|
25 | |||
25 | class IntegrationTypeRegistry(dict): |
|
26 | class IntegrationTypeRegistry(collections.OrderedDict): | |
26 | """ |
|
27 | """ | |
27 | Registry Class to hold IntegrationTypes |
|
28 | Registry Class to hold IntegrationTypes | |
28 | """ |
|
29 | """ |
@@ -43,15 +43,15 b' class TestGlobalIntegrationsView(TestInt' | |||||
43 | response = self.app.get(url) |
|
43 | response = self.app.get(url) | |
44 |
|
44 | |||
45 | assert response.status_code == 200 |
|
45 | assert response.status_code == 200 | |
46 | assert 'exist yet' in response.body |
|
46 | response.mustcontain('exist yet') | |
47 |
|
47 | |||
48 | def test_index_with_integrations(self, global_integration_stub): |
|
48 | def test_index_with_integrations(self, global_integration_stub): | |
49 | url = ADMIN_PREFIX + '/integrations' |
|
49 | url = ADMIN_PREFIX + '/integrations' | |
50 | response = self.app.get(url) |
|
50 | response = self.app.get(url) | |
51 |
|
51 | |||
52 | assert response.status_code == 200 |
|
52 | assert response.status_code == 200 | |
53 | assert 'exist yet' not in response.body |
|
53 | response.mustcontain(no=['exist yet']) | |
54 |
|
|
54 | response.mustcontain(global_integration_stub.name) | |
55 |
|
55 | |||
56 | @pytest.mark.parametrize( |
|
56 | @pytest.mark.parametrize( | |
57 | 'IntegrationType', integration_type_registry.values()) |
|
57 | 'IntegrationType', integration_type_registry.values()) | |
@@ -59,27 +59,29 b' class TestGlobalIntegrationsView(TestInt' | |||||
59 | url = ADMIN_PREFIX + '/integrations/new' |
|
59 | url = ADMIN_PREFIX + '/integrations/new' | |
60 |
|
60 | |||
61 | response = self.app.get(url, status=200) |
|
61 | response = self.app.get(url, status=200) | |
62 |
|
62 | if not IntegrationType.is_dummy: | ||
63 | url = (ADMIN_PREFIX + '/integrations/{integration}/new').format( |
|
63 | url = (ADMIN_PREFIX + '/integrations/{integration}/new').format( | |
64 | integration=IntegrationType.key) |
|
64 | integration=IntegrationType.key) | |
65 | assert url in response.body |
|
65 | response.mustcontain(url) | |
66 |
|
66 | |||
67 | @pytest.mark.parametrize( |
|
67 | @pytest.mark.parametrize( | |
68 | 'IntegrationType', integration_type_registry.values()) |
|
68 | 'IntegrationType', integration_type_registry.values()) | |
69 | def test_get_create_integration_page(self, IntegrationType): |
|
69 | def test_get_create_integration_page(self, IntegrationType): | |
70 | url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format( |
|
70 | url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format( | |
71 | integration_key=IntegrationType.key) |
|
71 | integration_key=IntegrationType.key) | |
72 |
|
72 | if IntegrationType.is_dummy: | ||
73 |
|
|
73 | self.app.get(url, status=404) | |
74 |
|
74 | else: | ||
75 | assert IntegrationType.display_name in response.body |
|
75 | response = self.app.get(url, status=200) | |
|
76 | response.mustcontain(IntegrationType.display_name) | |||
76 |
|
77 | |||
77 | def test_post_integration_page(self, StubIntegrationType, csrf_token, |
|
78 | def test_post_integration_page(self, StubIntegrationType, csrf_token, | |
78 | test_repo_group, backend_random): |
|
79 | test_repo_group, backend_random): | |
79 | url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format( |
|
80 | url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format( | |
80 | integration_key=StubIntegrationType.key) |
|
81 | integration_key=StubIntegrationType.key) | |
81 |
|
82 | |||
82 |
_post_integration_test_helper( |
|
83 | _post_integration_test_helper( | |
|
84 | self.app, url, csrf_token, admin_view=True, | |||
83 | repo=backend_random.repo, repo_group=test_repo_group) |
|
85 | repo=backend_random.repo, repo_group=test_repo_group) | |
84 |
|
86 | |||
85 |
|
87 | |||
@@ -90,7 +92,7 b' class TestRepoIntegrationsView(TestInteg' | |||||
90 | response = self.app.get(url) |
|
92 | response = self.app.get(url) | |
91 |
|
93 | |||
92 | assert response.status_code == 200 |
|
94 | assert response.status_code == 200 | |
93 | assert 'exist yet' in response.body |
|
95 | response.mustcontain('exist yet') | |
94 |
|
96 | |||
95 | def test_index_with_integrations(self, repo_integration_stub): |
|
97 | def test_index_with_integrations(self, repo_integration_stub): | |
96 | url = '/{repo_name}/settings/integrations'.format( |
|
98 | url = '/{repo_name}/settings/integrations'.format( | |
@@ -100,8 +102,8 b' class TestRepoIntegrationsView(TestInteg' | |||||
100 | response = self.app.get(url) |
|
102 | response = self.app.get(url) | |
101 |
|
103 | |||
102 | assert response.status_code == 200 |
|
104 | assert response.status_code == 200 | |
103 | assert stub_name in response.body |
|
105 | response.mustcontain(stub_name) | |
104 | assert 'exist yet' not in response.body |
|
106 | response.mustcontain(no=['exist yet']) | |
105 |
|
107 | |||
106 | @pytest.mark.parametrize( |
|
108 | @pytest.mark.parametrize( | |
107 | 'IntegrationType', integration_type_registry.values()) |
|
109 | 'IntegrationType', integration_type_registry.values()) | |
@@ -115,8 +117,8 b' class TestRepoIntegrationsView(TestInteg' | |||||
115 | url = '/{repo_name}/settings/integrations/{integration}/new'.format( |
|
117 | url = '/{repo_name}/settings/integrations/{integration}/new'.format( | |
116 | repo_name=repo_name, |
|
118 | repo_name=repo_name, | |
117 | integration=IntegrationType.key) |
|
119 | integration=IntegrationType.key) | |
118 |
|
120 | if not IntegrationType.is_dummy: | ||
119 | assert url in response.body |
|
121 | response.mustcontain(url) | |
120 |
|
122 | |||
121 | @pytest.mark.parametrize( |
|
123 | @pytest.mark.parametrize( | |
122 | 'IntegrationType', integration_type_registry.values()) |
|
124 | 'IntegrationType', integration_type_registry.values()) | |
@@ -124,10 +126,11 b' class TestRepoIntegrationsView(TestInteg' | |||||
124 | repo_name = backend_random.repo.repo_name |
|
126 | repo_name = backend_random.repo.repo_name | |
125 | url = '/{repo_name}/settings/integrations/{integration_key}/new'.format( |
|
127 | url = '/{repo_name}/settings/integrations/{integration_key}/new'.format( | |
126 | repo_name=repo_name, integration_key=IntegrationType.key) |
|
128 | repo_name=repo_name, integration_key=IntegrationType.key) | |
127 |
|
129 | if IntegrationType.is_dummy: | ||
128 |
|
|
130 | self.app.get(url, status=404) | |
129 |
|
131 | else: | ||
130 | assert IntegrationType.display_name in response.body |
|
132 | response = self.app.get(url, status=200) | |
|
133 | response.mustcontain(IntegrationType.display_name) | |||
131 |
|
134 | |||
132 | def test_post_integration_page(self, backend_random, test_repo_group, |
|
135 | def test_post_integration_page(self, backend_random, test_repo_group, | |
133 | StubIntegrationType, csrf_token): |
|
136 | StubIntegrationType, csrf_token): | |
@@ -147,7 +150,7 b' class TestRepoGroupIntegrationsView(Test' | |||||
147 | response = self.app.get(url) |
|
150 | response = self.app.get(url) | |
148 |
|
151 | |||
149 | assert response.status_code == 200 |
|
152 | assert response.status_code == 200 | |
150 | assert 'exist yet' in response.body |
|
153 | response.mustcontain('exist yet') | |
151 |
|
154 | |||
152 | def test_index_with_integrations( |
|
155 | def test_index_with_integrations( | |
153 | self, test_repo_group, repogroup_integration_stub): |
|
156 | self, test_repo_group, repogroup_integration_stub): | |
@@ -159,8 +162,8 b' class TestRepoGroupIntegrationsView(Test' | |||||
159 | response = self.app.get(url) |
|
162 | response = self.app.get(url) | |
160 |
|
163 | |||
161 | assert response.status_code == 200 |
|
164 | assert response.status_code == 200 | |
162 | assert 'exist yet' not in response.body |
|
165 | response.mustcontain(no=['exist yet']) | |
163 | assert stub_name in response.body |
|
166 | response.mustcontain(stub_name) | |
164 |
|
167 | |||
165 | def test_new_integration_page(self, test_repo_group): |
|
168 | def test_new_integration_page(self, test_repo_group): | |
166 | repo_group_name = test_repo_group.group_name |
|
169 | repo_group_name = test_repo_group.group_name | |
@@ -171,12 +174,13 b' class TestRepoGroupIntegrationsView(Test' | |||||
171 |
|
174 | |||
172 | assert response.status_code == 200 |
|
175 | assert response.status_code == 200 | |
173 |
|
176 | |||
174 | for integration_key in integration_type_registry: |
|
177 | for integration_key, integration_obj in integration_type_registry.items(): | |
175 | nurl = ('/{repo_group_name}/settings/integrations/{integration}/new').format( |
|
178 | if not integration_obj.is_dummy: | |
|
179 | nurl = ( | |||
|
180 | '/{repo_group_name}/settings/integrations/{integration}/new').format( | |||
176 | repo_group_name=repo_group_name, |
|
181 | repo_group_name=repo_group_name, | |
177 | integration=integration_key) |
|
182 | integration=integration_key) | |
178 |
|
183 | response.mustcontain(nurl) | ||
179 | assert nurl in response.body |
|
|||
180 |
|
184 | |||
181 | @pytest.mark.parametrize( |
|
185 | @pytest.mark.parametrize( | |
182 | 'IntegrationType', integration_type_registry.values()) |
|
186 | 'IntegrationType', integration_type_registry.values()) | |
@@ -188,10 +192,11 b' class TestRepoGroupIntegrationsView(Test' | |||||
188 | ).format(repo_group_name=repo_group_name, |
|
192 | ).format(repo_group_name=repo_group_name, | |
189 | integration_key=IntegrationType.key) |
|
193 | integration_key=IntegrationType.key) | |
190 |
|
194 | |||
191 | response = self.app.get(url) |
|
195 | if not IntegrationType.is_dummy: | |
192 |
|
196 | response = self.app.get(url, status=200) | ||
193 | assert response.status_code == 200 |
|
197 | response.mustcontain(IntegrationType.display_name) | |
194 | assert IntegrationType.display_name in response.body |
|
198 | else: | |
|
199 | self.app.get(url, status=404) | |||
195 |
|
200 | |||
196 | def test_post_integration_page(self, test_repo_group, backend_random, |
|
201 | def test_post_integration_page(self, test_repo_group, backend_random, | |
197 | StubIntegrationType, csrf_token): |
|
202 | StubIntegrationType, csrf_token): | |
@@ -217,7 +222,7 b' def _post_integration_test_helper(app, u' | |||||
217 | app.post(url, params={}, status=403) # missing csrf check |
|
222 | app.post(url, params={}, status=403) # missing csrf check | |
218 | response = app.post(url, params={'csrf_token': csrf_token}) |
|
223 | response = app.post(url, params={'csrf_token': csrf_token}) | |
219 | assert response.status_code == 200 |
|
224 | assert response.status_code == 200 | |
220 | assert 'Errors exist' in response.body |
|
225 | response.mustcontain('Errors exist') | |
221 |
|
226 | |||
222 | scopes_destinations = [ |
|
227 | scopes_destinations = [ | |
223 | ('global', |
|
228 | ('global', |
@@ -24,7 +24,7 b' from rhodecode.translation import _' | |||||
24 |
|
24 | |||
25 | class IntegrationTypeBase(object): |
|
25 | class IntegrationTypeBase(object): | |
26 | """ Base class for IntegrationType plugins """ |
|
26 | """ Base class for IntegrationType plugins """ | |
27 |
|
27 | is_dummy = False | ||
28 | description = '' |
|
28 | description = '' | |
29 | icon = ''' |
|
29 | icon = ''' | |
30 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
30 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
@@ -99,3 +99,12 b' class IntegrationTypeBase(object):' | |||||
99 | A colander schema of settings for the integration type |
|
99 | A colander schema of settings for the integration type | |
100 | """ |
|
100 | """ | |
101 | return colander.Schema() |
|
101 | return colander.Schema() | |
|
102 | ||||
|
103 | ||||
|
104 | class EEIntegration(IntegrationTypeBase): | |||
|
105 | description = 'Integration available in RhodeCode EE edition.' | |||
|
106 | is_dummy = True | |||
|
107 | ||||
|
108 | def __init__(self, name, key, settings=None): | |||
|
109 | self.display_name = name | |||
|
110 | self.key = key |
@@ -23,7 +23,7 b' import logging' | |||||
23 | import peppercorn |
|
23 | import peppercorn | |
24 | import webhelpers.paginate |
|
24 | import webhelpers.paginate | |
25 |
|
25 | |||
26 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden |
|
26 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound | |
27 |
|
27 | |||
28 | from rhodecode.apps._base import BaseAppView |
|
28 | from rhodecode.apps._base import BaseAppView | |
29 | from rhodecode.integrations import integration_type_registry |
|
29 | from rhodecode.integrations import integration_type_registry | |
@@ -76,7 +76,12 b' class IntegrationSettingsViewBase(BaseAp' | |||||
76 |
|
76 | |||
77 | if 'integration' in request.matchdict: # integration type context |
|
77 | if 'integration' in request.matchdict: # integration type context | |
78 | integration_type = request.matchdict['integration'] |
|
78 | integration_type = request.matchdict['integration'] | |
|
79 | if integration_type not in integration_type_registry: | |||
|
80 | raise HTTPNotFound() | |||
|
81 | ||||
79 | self.IntegrationType = integration_type_registry[integration_type] |
|
82 | self.IntegrationType = integration_type_registry[integration_type] | |
|
83 | if self.IntegrationType.is_dummy: | |||
|
84 | raise HTTPNotFound() | |||
80 |
|
85 | |||
81 | if 'integration_id' in request.matchdict: # single integration context |
|
86 | if 'integration_id' in request.matchdict: # single integration context | |
82 | integration_id = request.matchdict['integration_id'] |
|
87 | integration_id = request.matchdict['integration_id'] |
@@ -1195,6 +1195,9 b' table.integrations {' | |||||
1195 | min-width: 140px; |
|
1195 | min-width: 140px; | |
1196 | } |
|
1196 | } | |
1197 | } |
|
1197 | } | |
|
1198 | a.integration-box.dummy-integration { | |||
|
1199 | color: @grey4 | |||
|
1200 | } | |||
1198 | } |
|
1201 | } | |
1199 |
|
1202 | |||
1200 | //Permissions Settings |
|
1203 | //Permissions Settings |
@@ -64,23 +64,25 b'' | |||||
64 | <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a> |
|
64 | <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a> | |
65 |
|
65 | |||
66 | %for integration_key, IntegrationType in c.available_integrations.items(): |
|
66 | %for integration_key, IntegrationType in c.available_integrations.items(): | |
67 | <% |
|
67 | % if not IntegrationType.is_dummy: | |
68 | if c.repo: |
|
68 | <% | |
69 | list_url = request.route_path('repo_integrations_list', |
|
69 | if c.repo: | |
70 | repo_name=c.repo.repo_name, |
|
70 | list_url = request.route_path('repo_integrations_list', | |
71 |
|
|
71 | repo_name=c.repo.repo_name, | |
72 | elif c.repo_group: |
|
72 | integration=integration_key) | |
73 | list_url = request.route_path('repo_group_integrations_list', |
|
73 | elif c.repo_group: | |
74 | repo_group_name=c.repo_group.group_name, |
|
74 | list_url = request.route_path('repo_group_integrations_list', | |
75 |
|
|
75 | repo_group_name=c.repo_group.group_name, | |
76 | else: |
|
76 | integration=integration_key) | |
77 | list_url = request.route_path('global_integrations_list', |
|
77 | else: | |
78 | integration=integration_key) |
|
78 | list_url = request.route_path('global_integrations_list', | |
79 | %> |
|
79 | integration=integration_key) | |
80 | <a href="${list_url}" |
|
80 | %> | |
81 | class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}"> |
|
81 | <a href="${list_url}" | |
82 | ${IntegrationType.display_name} |
|
82 | class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}"> | |
83 | </a> |
|
83 | ${IntegrationType.display_name} | |
|
84 | </a> | |||
|
85 | % endif | |||
84 | %endfor |
|
86 | %endfor | |
85 |
|
87 | |||
86 | <% |
|
88 | <% |
@@ -36,7 +36,7 b'' | |||||
36 | %endif |
|
36 | %endif | |
37 | </%def> |
|
37 | </%def> | |
38 |
|
38 | |||
39 |
%for integration, Integration |
|
39 | %for integration, IntegrationObject in c.available_integrations.items(): | |
40 | <% |
|
40 | <% | |
41 | if c.repo: |
|
41 | if c.repo: | |
42 | create_url = request.route_path('repo_integrations_create', |
|
42 | create_url = request.route_path('repo_integrations_create', | |
@@ -49,16 +49,18 b'' | |||||
49 | else: |
|
49 | else: | |
50 | create_url = request.route_path('global_integrations_create', |
|
50 | create_url = request.route_path('global_integrations_create', | |
51 | integration=integration) |
|
51 | integration=integration) | |
|
52 | if IntegrationObject.is_dummy: | |||
|
53 | create_url = request.current_route_path() | |||
52 | %> |
|
54 | %> | |
53 | <a href="${create_url}" class="integration-box"> |
|
55 | <a href="${create_url}" class="integration-box ${'dummy-integration' if IntegrationObject.is_dummy else ''}"> | |
54 | <%widgets:panel> |
|
56 | <%widgets:panel> | |
55 | <h2> |
|
57 | <h2> | |
56 | <div class="integration-icon"> |
|
58 | <div class="integration-icon"> | |
57 |
${Integration |
|
59 | ${IntegrationObject.icon|n} | |
58 | </div> |
|
60 | </div> | |
59 |
${Integration |
|
61 | ${IntegrationObject.display_name} | |
60 | </h2> |
|
62 | </h2> | |
61 |
${Integration |
|
63 | ${IntegrationObject.description or _('No description available')} | |
62 | </%widgets:panel> |
|
64 | </%widgets:panel> | |
63 | </a> |
|
65 | </a> | |
64 | %endfor |
|
66 | %endfor |
General Comments 0
You need to be logged in to leave comments.
Login now