Show More
The requested changes are too big and content was truncated. Show full diff
@@ -1,397 +1,397 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2010-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2010-2020 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 itertools |
|
22 | import itertools | |
23 | import logging |
|
23 | import logging | |
24 | import collections |
|
24 | import collections | |
25 |
|
25 | |||
26 | from rhodecode.model import BaseModel |
|
26 | from rhodecode.model import BaseModel | |
27 | from rhodecode.model.db import ( |
|
27 | from rhodecode.model.db import ( | |
28 | ChangesetStatus, ChangesetComment, PullRequest, Session) |
|
28 | ChangesetStatus, ChangesetComment, PullRequest, Session) | |
29 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError |
|
29 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError | |
30 | from rhodecode.lib.markup_renderer import ( |
|
30 | from rhodecode.lib.markup_renderer import ( | |
31 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) |
|
31 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) | |
32 |
|
32 | |||
33 | log = logging.getLogger(__name__) |
|
33 | log = logging.getLogger(__name__) | |
34 |
|
34 | |||
35 |
|
35 | |||
36 | class ChangesetStatusModel(BaseModel): |
|
36 | class ChangesetStatusModel(BaseModel): | |
37 |
|
37 | |||
38 | cls = ChangesetStatus |
|
38 | cls = ChangesetStatus | |
39 |
|
39 | |||
40 | def __get_changeset_status(self, changeset_status): |
|
40 | def __get_changeset_status(self, changeset_status): | |
41 | return self._get_instance(ChangesetStatus, changeset_status) |
|
41 | return self._get_instance(ChangesetStatus, changeset_status) | |
42 |
|
42 | |||
43 | def __get_pull_request(self, pull_request): |
|
43 | def __get_pull_request(self, pull_request): | |
44 | return self._get_instance(PullRequest, pull_request) |
|
44 | return self._get_instance(PullRequest, pull_request) | |
45 |
|
45 | |||
46 | def _get_status_query(self, repo, revision, pull_request, |
|
46 | def _get_status_query(self, repo, revision, pull_request, | |
47 | with_revisions=False): |
|
47 | with_revisions=False): | |
48 | repo = self._get_repo(repo) |
|
48 | repo = self._get_repo(repo) | |
49 |
|
49 | |||
50 | q = ChangesetStatus.query()\ |
|
50 | q = ChangesetStatus.query()\ | |
51 | .filter(ChangesetStatus.repo == repo) |
|
51 | .filter(ChangesetStatus.repo == repo) | |
52 | if not with_revisions: |
|
52 | if not with_revisions: | |
53 | q = q.filter(ChangesetStatus.version == 0) |
|
53 | q = q.filter(ChangesetStatus.version == 0) | |
54 |
|
54 | |||
55 | if revision: |
|
55 | if revision: | |
56 | q = q.filter(ChangesetStatus.revision == revision) |
|
56 | q = q.filter(ChangesetStatus.revision == revision) | |
57 | elif pull_request: |
|
57 | elif pull_request: | |
58 | pull_request = self.__get_pull_request(pull_request) |
|
58 | pull_request = self.__get_pull_request(pull_request) | |
59 | # TODO: johbo: Think about the impact of this join, there must |
|
59 | # TODO: johbo: Think about the impact of this join, there must | |
60 | # be a reason why ChangesetStatus and ChanagesetComment is linked |
|
60 | # be a reason why ChangesetStatus and ChanagesetComment is linked | |
61 | # to the pull request. Might be that we want to do the same for |
|
61 | # to the pull request. Might be that we want to do the same for | |
62 | # the pull_request_version_id. |
|
62 | # the pull_request_version_id. | |
63 | q = q.join(ChangesetComment).filter( |
|
63 | q = q.join(ChangesetComment).filter( | |
64 | ChangesetStatus.pull_request == pull_request, |
|
64 | ChangesetStatus.pull_request == pull_request, | |
65 | ChangesetComment.pull_request_version_id == None) |
|
65 | ChangesetComment.pull_request_version_id == None) | |
66 | else: |
|
66 | else: | |
67 | raise Exception('Please specify revision or pull_request') |
|
67 | raise Exception('Please specify revision or pull_request') | |
68 | q = q.order_by(ChangesetStatus.version.asc()) |
|
68 | q = q.order_by(ChangesetStatus.version.asc()) | |
69 | return q |
|
69 | return q | |
70 |
|
70 | |||
71 | def calculate_group_vote(self, group_id, group_statuses_by_reviewers, |
|
71 | def calculate_group_vote(self, group_id, group_statuses_by_reviewers, | |
72 | trim_votes=True): |
|
72 | trim_votes=True): | |
73 | """ |
|
73 | """ | |
74 | Calculate status based on given group members, and voting rule |
|
74 | Calculate status based on given group members, and voting rule | |
75 |
|
75 | |||
76 |
|
76 | |||
77 | group1 - 4 members, 3 required for approval |
|
77 | group1 - 4 members, 3 required for approval | |
78 | user1 - approved |
|
78 | user1 - approved | |
79 | user2 - reject |
|
79 | user2 - reject | |
80 | user3 - approved |
|
80 | user3 - approved | |
81 | user4 - rejected |
|
81 | user4 - rejected | |
82 |
|
82 | |||
83 | final_state: rejected, reasons not at least 3 votes |
|
83 | final_state: rejected, reasons not at least 3 votes | |
84 |
|
84 | |||
85 |
|
85 | |||
86 | group1 - 4 members, 2 required for approval |
|
86 | group1 - 4 members, 2 required for approval | |
87 | user1 - approved |
|
87 | user1 - approved | |
88 | user2 - reject |
|
88 | user2 - reject | |
89 | user3 - approved |
|
89 | user3 - approved | |
90 | user4 - rejected |
|
90 | user4 - rejected | |
91 |
|
91 | |||
92 | final_state: approved, reasons got at least 2 approvals |
|
92 | final_state: approved, reasons got at least 2 approvals | |
93 |
|
93 | |||
94 | group1 - 4 members, ALL required for approval |
|
94 | group1 - 4 members, ALL required for approval | |
95 | user1 - approved |
|
95 | user1 - approved | |
96 | user2 - reject |
|
96 | user2 - reject | |
97 | user3 - approved |
|
97 | user3 - approved | |
98 | user4 - rejected |
|
98 | user4 - rejected | |
99 |
|
99 | |||
100 | final_state: rejected, reasons not all approvals |
|
100 | final_state: rejected, reasons not all approvals | |
101 |
|
101 | |||
102 |
|
102 | |||
103 | group1 - 4 members, ALL required for approval |
|
103 | group1 - 4 members, ALL required for approval | |
104 | user1 - approved |
|
104 | user1 - approved | |
105 | user2 - approved |
|
105 | user2 - approved | |
106 | user3 - approved |
|
106 | user3 - approved | |
107 | user4 - approved |
|
107 | user4 - approved | |
108 |
|
108 | |||
109 | final_state: approved, reason all approvals received |
|
109 | final_state: approved, reason all approvals received | |
110 |
|
110 | |||
111 | group1 - 4 members, 5 required for approval |
|
111 | group1 - 4 members, 5 required for approval | |
112 | (approval should be shorted to number of actual members) |
|
112 | (approval should be shorted to number of actual members) | |
113 |
|
113 | |||
114 | user1 - approved |
|
114 | user1 - approved | |
115 | user2 - approved |
|
115 | user2 - approved | |
116 | user3 - approved |
|
116 | user3 - approved | |
117 | user4 - approved |
|
117 | user4 - approved | |
118 |
|
118 | |||
119 | final_state: approved, reason all approvals received |
|
119 | final_state: approved, reason all approvals received | |
120 |
|
120 | |||
121 | """ |
|
121 | """ | |
122 | group_vote_data = {} |
|
122 | group_vote_data = {} | |
123 | got_rule = False |
|
123 | got_rule = False | |
124 | members = collections.OrderedDict() |
|
124 | members = collections.OrderedDict() | |
125 | for review_obj, user, reasons, mandatory, statuses \ |
|
125 | for review_obj, user, reasons, mandatory, statuses \ | |
126 | in group_statuses_by_reviewers: |
|
126 | in group_statuses_by_reviewers: | |
127 |
|
127 | |||
128 | if not got_rule: |
|
128 | if not got_rule: | |
129 | group_vote_data = review_obj.rule_user_group_data() |
|
129 | group_vote_data = review_obj.rule_user_group_data() | |
130 | got_rule = bool(group_vote_data) |
|
130 | got_rule = bool(group_vote_data) | |
131 |
|
131 | |||
132 | members[user.user_id] = statuses |
|
132 | members[user.user_id] = statuses | |
133 |
|
133 | |||
134 | if not group_vote_data: |
|
134 | if not group_vote_data: | |
135 | return [] |
|
135 | return [] | |
136 |
|
136 | |||
137 | required_votes = group_vote_data['vote_rule'] |
|
137 | required_votes = group_vote_data['vote_rule'] | |
138 | if required_votes == -1: |
|
138 | if required_votes == -1: | |
139 | # -1 means all required, so we replace it with how many people |
|
139 | # -1 means all required, so we replace it with how many people | |
140 | # are in the members |
|
140 | # are in the members | |
141 | required_votes = len(members) |
|
141 | required_votes = len(members) | |
142 |
|
142 | |||
143 | if trim_votes and required_votes > len(members): |
|
143 | if trim_votes and required_votes > len(members): | |
144 | # we require more votes than we have members in the group |
|
144 | # we require more votes than we have members in the group | |
145 | # in this case we trim the required votes to the number of members |
|
145 | # in this case we trim the required votes to the number of members | |
146 | required_votes = len(members) |
|
146 | required_votes = len(members) | |
147 |
|
147 | |||
148 | approvals = sum([ |
|
148 | approvals = sum([ | |
149 | 1 for statuses in members.values() |
|
149 | 1 for statuses in members.values() | |
150 | if statuses and |
|
150 | if statuses and | |
151 | statuses[0][1].status == ChangesetStatus.STATUS_APPROVED]) |
|
151 | statuses[0][1].status == ChangesetStatus.STATUS_APPROVED]) | |
152 |
|
152 | |||
153 | calculated_votes = [] |
|
153 | calculated_votes = [] | |
154 | # we have all votes from users, now check if we have enough votes |
|
154 | # we have all votes from users, now check if we have enough votes | |
155 | # to fill other |
|
155 | # to fill other | |
156 | fill_in = ChangesetStatus.STATUS_UNDER_REVIEW |
|
156 | fill_in = ChangesetStatus.STATUS_UNDER_REVIEW | |
157 | if approvals >= required_votes: |
|
157 | if approvals >= required_votes: | |
158 | fill_in = ChangesetStatus.STATUS_APPROVED |
|
158 | fill_in = ChangesetStatus.STATUS_APPROVED | |
159 |
|
159 | |||
160 | for member, statuses in members.items(): |
|
160 | for member, statuses in members.items(): | |
161 | if statuses: |
|
161 | if statuses: | |
162 | ver, latest = statuses[0] |
|
162 | ver, latest = statuses[0] | |
163 | if fill_in == ChangesetStatus.STATUS_APPROVED: |
|
163 | if fill_in == ChangesetStatus.STATUS_APPROVED: | |
164 | calculated_votes.append(fill_in) |
|
164 | calculated_votes.append(fill_in) | |
165 | else: |
|
165 | else: | |
166 | calculated_votes.append(latest.status) |
|
166 | calculated_votes.append(latest.status) | |
167 | else: |
|
167 | else: | |
168 | calculated_votes.append(fill_in) |
|
168 | calculated_votes.append(fill_in) | |
169 |
|
169 | |||
170 | return calculated_votes |
|
170 | return calculated_votes | |
171 |
|
171 | |||
172 | def calculate_status(self, statuses_by_reviewers): |
|
172 | def calculate_status(self, statuses_by_reviewers): | |
173 | """ |
|
173 | """ | |
174 | Given the approval statuses from reviewers, calculates final approval |
|
174 | Given the approval statuses from reviewers, calculates final approval | |
175 | status. There can only be 3 results, all approved, all rejected. If |
|
175 | status. There can only be 3 results, all approved, all rejected. If | |
176 | there is no consensus the PR is under review. |
|
176 | there is no consensus the PR is under review. | |
177 |
|
177 | |||
178 | :param statuses_by_reviewers: |
|
178 | :param statuses_by_reviewers: | |
179 | """ |
|
179 | """ | |
180 |
|
180 | |||
181 | def group_rule(element): |
|
181 | def group_rule(element): | |
182 | review_obj = element[0] |
|
182 | review_obj = element[0] | |
183 | rule_data = review_obj.rule_user_group_data() |
|
183 | rule_data = review_obj.rule_user_group_data() | |
184 | if rule_data and rule_data['id']: |
|
184 | if rule_data and rule_data['id']: | |
185 | return rule_data['id'] |
|
185 | return rule_data['id'] | |
186 |
|
186 | |||
187 | voting_groups = itertools.groupby( |
|
187 | voting_groups = itertools.groupby( | |
188 | sorted(statuses_by_reviewers, key=group_rule), group_rule) |
|
188 | sorted(statuses_by_reviewers, key=group_rule), group_rule) | |
189 |
|
189 | |||
190 | voting_by_groups = [(x, list(y)) for x, y in voting_groups] |
|
190 | voting_by_groups = [(x, list(y)) for x, y in voting_groups] | |
191 |
|
191 | |||
192 | reviewers_number = len(statuses_by_reviewers) |
|
192 | reviewers_number = len(statuses_by_reviewers) | |
193 | votes = collections.defaultdict(int) |
|
193 | votes = collections.defaultdict(int) | |
194 | for group, group_statuses_by_reviewers in voting_by_groups: |
|
194 | for group, group_statuses_by_reviewers in voting_by_groups: | |
195 | if group: |
|
195 | if group: | |
196 | # calculate how the "group" voted |
|
196 | # calculate how the "group" voted | |
197 | for vote_status in self.calculate_group_vote( |
|
197 | for vote_status in self.calculate_group_vote( | |
198 | group, group_statuses_by_reviewers): |
|
198 | group, group_statuses_by_reviewers): | |
199 | votes[vote_status] += 1 |
|
199 | votes[vote_status] += 1 | |
200 | else: |
|
200 | else: | |
201 |
|
201 | |||
202 | for review_obj, user, reasons, mandatory, statuses \ |
|
202 | for review_obj, user, reasons, mandatory, statuses \ | |
203 | in group_statuses_by_reviewers: |
|
203 | in group_statuses_by_reviewers: | |
204 | # individual vote |
|
204 | # individual vote | |
205 | if statuses: |
|
205 | if statuses: | |
206 | ver, latest = statuses[0] |
|
206 | ver, latest = statuses[0] | |
207 | votes[latest.status] += 1 |
|
207 | votes[latest.status] += 1 | |
208 |
|
208 | |||
209 | approved_votes_count = votes[ChangesetStatus.STATUS_APPROVED] |
|
209 | approved_votes_count = votes[ChangesetStatus.STATUS_APPROVED] | |
210 | rejected_votes_count = votes[ChangesetStatus.STATUS_REJECTED] |
|
210 | rejected_votes_count = votes[ChangesetStatus.STATUS_REJECTED] | |
211 |
|
211 | |||
212 | # TODO(marcink): with group voting, how does rejected work, |
|
212 | # TODO(marcink): with group voting, how does rejected work, | |
213 | # do we ever get rejected state ? |
|
213 | # do we ever get rejected state ? | |
214 |
|
214 | |||
215 | if approved_votes_count == reviewers_number: |
|
215 | if approved_votes_count and (approved_votes_count == reviewers_number): | |
216 | return ChangesetStatus.STATUS_APPROVED |
|
216 | return ChangesetStatus.STATUS_APPROVED | |
217 |
|
217 | |||
218 | if rejected_votes_count == reviewers_number: |
|
218 | if rejected_votes_count and (rejected_votes_count == reviewers_number): | |
219 | return ChangesetStatus.STATUS_REJECTED |
|
219 | return ChangesetStatus.STATUS_REJECTED | |
220 |
|
220 | |||
221 | return ChangesetStatus.STATUS_UNDER_REVIEW |
|
221 | return ChangesetStatus.STATUS_UNDER_REVIEW | |
222 |
|
222 | |||
223 | def get_statuses(self, repo, revision=None, pull_request=None, |
|
223 | def get_statuses(self, repo, revision=None, pull_request=None, | |
224 | with_revisions=False): |
|
224 | with_revisions=False): | |
225 | q = self._get_status_query(repo, revision, pull_request, |
|
225 | q = self._get_status_query(repo, revision, pull_request, | |
226 | with_revisions) |
|
226 | with_revisions) | |
227 | return q.all() |
|
227 | return q.all() | |
228 |
|
228 | |||
229 | def get_status(self, repo, revision=None, pull_request=None, as_str=True): |
|
229 | def get_status(self, repo, revision=None, pull_request=None, as_str=True): | |
230 | """ |
|
230 | """ | |
231 | Returns latest status of changeset for given revision or for given |
|
231 | Returns latest status of changeset for given revision or for given | |
232 | pull request. Statuses are versioned inside a table itself and |
|
232 | pull request. Statuses are versioned inside a table itself and | |
233 | version == 0 is always the current one |
|
233 | version == 0 is always the current one | |
234 |
|
234 | |||
235 | :param repo: |
|
235 | :param repo: | |
236 | :param revision: 40char hash or None |
|
236 | :param revision: 40char hash or None | |
237 | :param pull_request: pull_request reference |
|
237 | :param pull_request: pull_request reference | |
238 | :param as_str: return status as string not object |
|
238 | :param as_str: return status as string not object | |
239 | """ |
|
239 | """ | |
240 | q = self._get_status_query(repo, revision, pull_request) |
|
240 | q = self._get_status_query(repo, revision, pull_request) | |
241 |
|
241 | |||
242 | # need to use first here since there can be multiple statuses |
|
242 | # need to use first here since there can be multiple statuses | |
243 | # returned from pull_request |
|
243 | # returned from pull_request | |
244 | status = q.first() |
|
244 | status = q.first() | |
245 | if as_str: |
|
245 | if as_str: | |
246 | status = status.status if status else status |
|
246 | status = status.status if status else status | |
247 | st = status or ChangesetStatus.DEFAULT |
|
247 | st = status or ChangesetStatus.DEFAULT | |
248 | return str(st) |
|
248 | return str(st) | |
249 | return status |
|
249 | return status | |
250 |
|
250 | |||
251 | def _render_auto_status_message( |
|
251 | def _render_auto_status_message( | |
252 | self, status, commit_id=None, pull_request=None): |
|
252 | self, status, commit_id=None, pull_request=None): | |
253 | """ |
|
253 | """ | |
254 | render the message using DEFAULT_COMMENTS_RENDERER (RST renderer), |
|
254 | render the message using DEFAULT_COMMENTS_RENDERER (RST renderer), | |
255 | so it's always looking the same disregarding on which default |
|
255 | so it's always looking the same disregarding on which default | |
256 | renderer system is using. |
|
256 | renderer system is using. | |
257 |
|
257 | |||
258 | :param status: status text to change into |
|
258 | :param status: status text to change into | |
259 | :param commit_id: the commit_id we change the status for |
|
259 | :param commit_id: the commit_id we change the status for | |
260 | :param pull_request: the pull request we change the status for |
|
260 | :param pull_request: the pull request we change the status for | |
261 | """ |
|
261 | """ | |
262 |
|
262 | |||
263 | new_status = ChangesetStatus.get_status_lbl(status) |
|
263 | new_status = ChangesetStatus.get_status_lbl(status) | |
264 |
|
264 | |||
265 | params = { |
|
265 | params = { | |
266 | 'new_status_label': new_status, |
|
266 | 'new_status_label': new_status, | |
267 | 'pull_request': pull_request, |
|
267 | 'pull_request': pull_request, | |
268 | 'commit_id': commit_id, |
|
268 | 'commit_id': commit_id, | |
269 | } |
|
269 | } | |
270 | renderer = RstTemplateRenderer() |
|
270 | renderer = RstTemplateRenderer() | |
271 | return renderer.render('auto_status_change.mako', **params) |
|
271 | return renderer.render('auto_status_change.mako', **params) | |
272 |
|
272 | |||
273 | def set_status(self, repo, status, user, comment=None, revision=None, |
|
273 | def set_status(self, repo, status, user, comment=None, revision=None, | |
274 | pull_request=None, dont_allow_on_closed_pull_request=False): |
|
274 | pull_request=None, dont_allow_on_closed_pull_request=False): | |
275 | """ |
|
275 | """ | |
276 | Creates new status for changeset or updates the old ones bumping their |
|
276 | Creates new status for changeset or updates the old ones bumping their | |
277 | version, leaving the current status at |
|
277 | version, leaving the current status at | |
278 |
|
278 | |||
279 | :param repo: |
|
279 | :param repo: | |
280 | :param revision: |
|
280 | :param revision: | |
281 | :param status: |
|
281 | :param status: | |
282 | :param user: |
|
282 | :param user: | |
283 | :param comment: |
|
283 | :param comment: | |
284 | :param dont_allow_on_closed_pull_request: don't allow a status change |
|
284 | :param dont_allow_on_closed_pull_request: don't allow a status change | |
285 | if last status was for pull request and it's closed. We shouldn't |
|
285 | if last status was for pull request and it's closed. We shouldn't | |
286 | mess around this manually |
|
286 | mess around this manually | |
287 | """ |
|
287 | """ | |
288 | repo = self._get_repo(repo) |
|
288 | repo = self._get_repo(repo) | |
289 |
|
289 | |||
290 | q = ChangesetStatus.query() |
|
290 | q = ChangesetStatus.query() | |
291 |
|
291 | |||
292 | if revision: |
|
292 | if revision: | |
293 | q = q.filter(ChangesetStatus.repo == repo) |
|
293 | q = q.filter(ChangesetStatus.repo == repo) | |
294 | q = q.filter(ChangesetStatus.revision == revision) |
|
294 | q = q.filter(ChangesetStatus.revision == revision) | |
295 | elif pull_request: |
|
295 | elif pull_request: | |
296 | pull_request = self.__get_pull_request(pull_request) |
|
296 | pull_request = self.__get_pull_request(pull_request) | |
297 | q = q.filter(ChangesetStatus.repo == pull_request.source_repo) |
|
297 | q = q.filter(ChangesetStatus.repo == pull_request.source_repo) | |
298 | q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions)) |
|
298 | q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions)) | |
299 | cur_statuses = q.all() |
|
299 | cur_statuses = q.all() | |
300 |
|
300 | |||
301 | # if statuses exists and last is associated with a closed pull request |
|
301 | # if statuses exists and last is associated with a closed pull request | |
302 | # we need to check if we can allow this status change |
|
302 | # we need to check if we can allow this status change | |
303 | if (dont_allow_on_closed_pull_request and cur_statuses |
|
303 | if (dont_allow_on_closed_pull_request and cur_statuses | |
304 | and getattr(cur_statuses[0].pull_request, 'status', '') |
|
304 | and getattr(cur_statuses[0].pull_request, 'status', '') | |
305 | == PullRequest.STATUS_CLOSED): |
|
305 | == PullRequest.STATUS_CLOSED): | |
306 | raise StatusChangeOnClosedPullRequestError( |
|
306 | raise StatusChangeOnClosedPullRequestError( | |
307 | 'Changing status on closed pull request is not allowed' |
|
307 | 'Changing status on closed pull request is not allowed' | |
308 | ) |
|
308 | ) | |
309 |
|
309 | |||
310 | # update all current statuses with older version |
|
310 | # update all current statuses with older version | |
311 | if cur_statuses: |
|
311 | if cur_statuses: | |
312 | for st in cur_statuses: |
|
312 | for st in cur_statuses: | |
313 | st.version += 1 |
|
313 | st.version += 1 | |
314 | Session().add(st) |
|
314 | Session().add(st) | |
315 | Session().flush() |
|
315 | Session().flush() | |
316 |
|
316 | |||
317 | def _create_status(user, repo, status, comment, revision, pull_request): |
|
317 | def _create_status(user, repo, status, comment, revision, pull_request): | |
318 | new_status = ChangesetStatus() |
|
318 | new_status = ChangesetStatus() | |
319 | new_status.author = self._get_user(user) |
|
319 | new_status.author = self._get_user(user) | |
320 | new_status.repo = self._get_repo(repo) |
|
320 | new_status.repo = self._get_repo(repo) | |
321 | new_status.status = status |
|
321 | new_status.status = status | |
322 | new_status.comment = comment |
|
322 | new_status.comment = comment | |
323 | new_status.revision = revision |
|
323 | new_status.revision = revision | |
324 | new_status.pull_request = pull_request |
|
324 | new_status.pull_request = pull_request | |
325 | return new_status |
|
325 | return new_status | |
326 |
|
326 | |||
327 | if not comment: |
|
327 | if not comment: | |
328 | from rhodecode.model.comment import CommentsModel |
|
328 | from rhodecode.model.comment import CommentsModel | |
329 | comment = CommentsModel().create( |
|
329 | comment = CommentsModel().create( | |
330 | text=self._render_auto_status_message( |
|
330 | text=self._render_auto_status_message( | |
331 | status, commit_id=revision, pull_request=pull_request), |
|
331 | status, commit_id=revision, pull_request=pull_request), | |
332 | repo=repo, |
|
332 | repo=repo, | |
333 | user=user, |
|
333 | user=user, | |
334 | pull_request=pull_request, |
|
334 | pull_request=pull_request, | |
335 | send_email=False, renderer=DEFAULT_COMMENTS_RENDERER |
|
335 | send_email=False, renderer=DEFAULT_COMMENTS_RENDERER | |
336 | ) |
|
336 | ) | |
337 |
|
337 | |||
338 | if revision: |
|
338 | if revision: | |
339 | new_status = _create_status( |
|
339 | new_status = _create_status( | |
340 | user=user, repo=repo, status=status, comment=comment, |
|
340 | user=user, repo=repo, status=status, comment=comment, | |
341 | revision=revision, pull_request=pull_request) |
|
341 | revision=revision, pull_request=pull_request) | |
342 | Session().add(new_status) |
|
342 | Session().add(new_status) | |
343 | return new_status |
|
343 | return new_status | |
344 | elif pull_request: |
|
344 | elif pull_request: | |
345 | # pull request can have more than one revision associated to it |
|
345 | # pull request can have more than one revision associated to it | |
346 | # we need to create new version for each one |
|
346 | # we need to create new version for each one | |
347 | new_statuses = [] |
|
347 | new_statuses = [] | |
348 | repo = pull_request.source_repo |
|
348 | repo = pull_request.source_repo | |
349 | for rev in pull_request.revisions: |
|
349 | for rev in pull_request.revisions: | |
350 | new_status = _create_status( |
|
350 | new_status = _create_status( | |
351 | user=user, repo=repo, status=status, comment=comment, |
|
351 | user=user, repo=repo, status=status, comment=comment, | |
352 | revision=rev, pull_request=pull_request) |
|
352 | revision=rev, pull_request=pull_request) | |
353 | new_statuses.append(new_status) |
|
353 | new_statuses.append(new_status) | |
354 | Session().add(new_status) |
|
354 | Session().add(new_status) | |
355 | return new_statuses |
|
355 | return new_statuses | |
356 |
|
356 | |||
357 | def aggregate_votes_by_user(self, commit_statuses, reviewers_data): |
|
357 | def aggregate_votes_by_user(self, commit_statuses, reviewers_data): | |
358 |
|
358 | |||
359 | commit_statuses_map = collections.defaultdict(list) |
|
359 | commit_statuses_map = collections.defaultdict(list) | |
360 | for st in commit_statuses: |
|
360 | for st in commit_statuses: | |
361 | commit_statuses_map[st.author.username] += [st] |
|
361 | commit_statuses_map[st.author.username] += [st] | |
362 |
|
362 | |||
363 | reviewers = [] |
|
363 | reviewers = [] | |
364 |
|
364 | |||
365 | def version(commit_status): |
|
365 | def version(commit_status): | |
366 | return commit_status.version |
|
366 | return commit_status.version | |
367 |
|
367 | |||
368 | for obj in reviewers_data: |
|
368 | for obj in reviewers_data: | |
369 | if not obj.user: |
|
369 | if not obj.user: | |
370 | continue |
|
370 | continue | |
371 | statuses = commit_statuses_map.get(obj.user.username, None) |
|
371 | statuses = commit_statuses_map.get(obj.user.username, None) | |
372 | if statuses: |
|
372 | if statuses: | |
373 | status_groups = itertools.groupby( |
|
373 | status_groups = itertools.groupby( | |
374 | sorted(statuses, key=version), version) |
|
374 | sorted(statuses, key=version), version) | |
375 | statuses = [(x, list(y)[0]) for x, y in status_groups] |
|
375 | statuses = [(x, list(y)[0]) for x, y in status_groups] | |
376 |
|
376 | |||
377 | reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses)) |
|
377 | reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses)) | |
378 |
|
378 | |||
379 | return reviewers |
|
379 | return reviewers | |
380 |
|
380 | |||
381 | def reviewers_statuses(self, pull_request): |
|
381 | def reviewers_statuses(self, pull_request): | |
382 | _commit_statuses = self.get_statuses( |
|
382 | _commit_statuses = self.get_statuses( | |
383 | pull_request.source_repo, |
|
383 | pull_request.source_repo, | |
384 | pull_request=pull_request, |
|
384 | pull_request=pull_request, | |
385 | with_revisions=True) |
|
385 | with_revisions=True) | |
386 |
|
386 | |||
387 | return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers) |
|
387 | return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers) | |
388 |
|
388 | |||
389 | def calculated_review_status(self, pull_request, reviewers_statuses=None): |
|
389 | def calculated_review_status(self, pull_request, reviewers_statuses=None): | |
390 | """ |
|
390 | """ | |
391 | calculate pull request status based on reviewers, it should be a list |
|
391 | calculate pull request status based on reviewers, it should be a list | |
392 | of two element lists. |
|
392 | of two element lists. | |
393 |
|
393 | |||
394 | :param reviewers_statuses: |
|
394 | :param reviewers_statuses: | |
395 | """ |
|
395 | """ | |
396 | reviewers = reviewers_statuses or self.reviewers_statuses(pull_request) |
|
396 | reviewers = reviewers_statuses or self.reviewers_statuses(pull_request) | |
397 | return self.calculate_status(reviewers) |
|
397 | return self.calculate_status(reviewers) |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,697 +1,706 b'' | |||||
1 | // # Copyright (C) 2010-2020 RhodeCode GmbH |
|
1 | // # Copyright (C) 2010-2020 RhodeCode GmbH | |
2 | // # |
|
2 | // # | |
3 | // # This program is free software: you can redistribute it and/or modify |
|
3 | // # This program is free software: you can redistribute it and/or modify | |
4 | // # it under the terms of the GNU Affero General Public License, version 3 |
|
4 | // # it under the terms of the GNU Affero General Public License, version 3 | |
5 | // # (only), as published by the Free Software Foundation. |
|
5 | // # (only), as published by the Free Software Foundation. | |
6 | // # |
|
6 | // # | |
7 | // # This program is distributed in the hope that it will be useful, |
|
7 | // # This program is distributed in the hope that it will be useful, | |
8 | // # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | // # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | // # GNU General Public License for more details. |
|
10 | // # GNU General Public License for more details. | |
11 | // # |
|
11 | // # | |
12 | // # You should have received a copy of the GNU Affero General Public License |
|
12 | // # You should have received a copy of the GNU Affero General Public License | |
13 | // # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | // # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | // # |
|
14 | // # | |
15 | // # This program is dual-licensed. If you wish to learn more about the |
|
15 | // # This program is dual-licensed. If you wish to learn more about the | |
16 | // # RhodeCode Enterprise Edition, including its added features, Support services, |
|
16 | // # RhodeCode Enterprise Edition, including its added features, Support services, | |
17 | // # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
17 | // # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
18 |
|
18 | |||
19 | /** |
|
19 | /** | |
20 | RhodeCode JS Files |
|
20 | RhodeCode JS Files | |
21 | **/ |
|
21 | **/ | |
22 |
|
22 | |||
23 | if (typeof console == "undefined" || typeof console.log == "undefined"){ |
|
23 | if (typeof console == "undefined" || typeof console.log == "undefined"){ | |
24 | console = { log: function() {} } |
|
24 | console = { log: function() {} } | |
25 | } |
|
25 | } | |
26 |
|
26 | |||
27 | // TODO: move the following function to submodules |
|
27 | // TODO: move the following function to submodules | |
28 |
|
28 | |||
29 | /** |
|
29 | /** | |
30 | * show more |
|
30 | * show more | |
31 | */ |
|
31 | */ | |
32 | var show_more_event = function(){ |
|
32 | var show_more_event = function(){ | |
33 | $('table .show_more').click(function(e) { |
|
33 | $('table .show_more').click(function(e) { | |
34 | var cid = e.target.id.substring(1); |
|
34 | var cid = e.target.id.substring(1); | |
35 | var button = $(this); |
|
35 | var button = $(this); | |
36 | if (button.hasClass('open')) { |
|
36 | if (button.hasClass('open')) { | |
37 | $('#'+cid).hide(); |
|
37 | $('#'+cid).hide(); | |
38 | button.removeClass('open'); |
|
38 | button.removeClass('open'); | |
39 | } else { |
|
39 | } else { | |
40 | $('#'+cid).show(); |
|
40 | $('#'+cid).show(); | |
41 | button.addClass('open one'); |
|
41 | button.addClass('open one'); | |
42 | } |
|
42 | } | |
43 | }); |
|
43 | }); | |
44 | }; |
|
44 | }; | |
45 |
|
45 | |||
46 | var compare_radio_buttons = function(repo_name, compare_ref_type){ |
|
46 | var compare_radio_buttons = function(repo_name, compare_ref_type){ | |
47 | $('#compare_action').on('click', function(e){ |
|
47 | $('#compare_action').on('click', function(e){ | |
48 | e.preventDefault(); |
|
48 | e.preventDefault(); | |
49 |
|
49 | |||
50 | var source = $('input[name=compare_source]:checked').val(); |
|
50 | var source = $('input[name=compare_source]:checked').val(); | |
51 | var target = $('input[name=compare_target]:checked').val(); |
|
51 | var target = $('input[name=compare_target]:checked').val(); | |
52 | if(source && target){ |
|
52 | if(source && target){ | |
53 | var url_data = { |
|
53 | var url_data = { | |
54 | repo_name: repo_name, |
|
54 | repo_name: repo_name, | |
55 | source_ref: source, |
|
55 | source_ref: source, | |
56 | source_ref_type: compare_ref_type, |
|
56 | source_ref_type: compare_ref_type, | |
57 | target_ref: target, |
|
57 | target_ref: target, | |
58 | target_ref_type: compare_ref_type, |
|
58 | target_ref_type: compare_ref_type, | |
59 | merge: 1 |
|
59 | merge: 1 | |
60 | }; |
|
60 | }; | |
61 | window.location = pyroutes.url('repo_compare', url_data); |
|
61 | window.location = pyroutes.url('repo_compare', url_data); | |
62 | } |
|
62 | } | |
63 | }); |
|
63 | }); | |
64 | $('.compare-radio-button').on('click', function(e){ |
|
64 | $('.compare-radio-button').on('click', function(e){ | |
65 | var source = $('input[name=compare_source]:checked').val(); |
|
65 | var source = $('input[name=compare_source]:checked').val(); | |
66 | var target = $('input[name=compare_target]:checked').val(); |
|
66 | var target = $('input[name=compare_target]:checked').val(); | |
67 | if(source && target){ |
|
67 | if(source && target){ | |
68 | $('#compare_action').removeAttr("disabled"); |
|
68 | $('#compare_action').removeAttr("disabled"); | |
69 | $('#compare_action').removeClass("disabled"); |
|
69 | $('#compare_action').removeClass("disabled"); | |
70 | } |
|
70 | } | |
71 | }) |
|
71 | }) | |
72 | }; |
|
72 | }; | |
73 |
|
73 | |||
74 | var showRepoSize = function(target, repo_name, commit_id, callback) { |
|
74 | var showRepoSize = function(target, repo_name, commit_id, callback) { | |
75 | var container = $('#' + target); |
|
75 | var container = $('#' + target); | |
76 | var url = pyroutes.url('repo_stats', |
|
76 | var url = pyroutes.url('repo_stats', | |
77 | {"repo_name": repo_name, "commit_id": commit_id}); |
|
77 | {"repo_name": repo_name, "commit_id": commit_id}); | |
78 |
|
78 | |||
79 | container.show(); |
|
79 | container.show(); | |
80 | if (!container.hasClass('loaded')) { |
|
80 | if (!container.hasClass('loaded')) { | |
81 | $.ajax({url: url}) |
|
81 | $.ajax({url: url}) | |
82 | .complete(function (data) { |
|
82 | .complete(function (data) { | |
83 | var responseJSON = data.responseJSON; |
|
83 | var responseJSON = data.responseJSON; | |
84 | container.addClass('loaded'); |
|
84 | container.addClass('loaded'); | |
85 | container.html(responseJSON.size); |
|
85 | container.html(responseJSON.size); | |
86 | callback(responseJSON.code_stats) |
|
86 | callback(responseJSON.code_stats) | |
87 | }) |
|
87 | }) | |
88 | .fail(function (data) { |
|
88 | .fail(function (data) { | |
89 | console.log('failed to load repo stats'); |
|
89 | console.log('failed to load repo stats'); | |
90 | }); |
|
90 | }); | |
91 | } |
|
91 | } | |
92 |
|
92 | |||
93 | }; |
|
93 | }; | |
94 |
|
94 | |||
95 | var showRepoStats = function(target, data){ |
|
95 | var showRepoStats = function(target, data){ | |
96 | var container = $('#' + target); |
|
96 | var container = $('#' + target); | |
97 |
|
97 | |||
98 | if (container.hasClass('loaded')) { |
|
98 | if (container.hasClass('loaded')) { | |
99 | return |
|
99 | return | |
100 | } |
|
100 | } | |
101 |
|
101 | |||
102 | var total = 0; |
|
102 | var total = 0; | |
103 | var no_data = true; |
|
103 | var no_data = true; | |
104 | var tbl = document.createElement('table'); |
|
104 | var tbl = document.createElement('table'); | |
105 | tbl.setAttribute('class', 'trending_language_tbl rctable'); |
|
105 | tbl.setAttribute('class', 'trending_language_tbl rctable'); | |
106 |
|
106 | |||
107 | $.each(data, function(key, val){ |
|
107 | $.each(data, function(key, val){ | |
108 | total += val.count; |
|
108 | total += val.count; | |
109 | }); |
|
109 | }); | |
110 |
|
110 | |||
111 | var sortedStats = []; |
|
111 | var sortedStats = []; | |
112 | for (var obj in data){ |
|
112 | for (var obj in data){ | |
113 | sortedStats.push([obj, data[obj]]) |
|
113 | sortedStats.push([obj, data[obj]]) | |
114 | } |
|
114 | } | |
115 | var sortedData = sortedStats.sort(function (a, b) { |
|
115 | var sortedData = sortedStats.sort(function (a, b) { | |
116 | return b[1].count - a[1].count |
|
116 | return b[1].count - a[1].count | |
117 | }); |
|
117 | }); | |
118 | var cnt = 0; |
|
118 | var cnt = 0; | |
119 | $.each(sortedData, function(idx, val){ |
|
119 | $.each(sortedData, function(idx, val){ | |
120 | cnt += 1; |
|
120 | cnt += 1; | |
121 | no_data = false; |
|
121 | no_data = false; | |
122 |
|
122 | |||
123 | var tr = document.createElement('tr'); |
|
123 | var tr = document.createElement('tr'); | |
124 |
|
124 | |||
125 | var key = val[0]; |
|
125 | var key = val[0]; | |
126 | var obj = {"desc": val[1].desc, "count": val[1].count}; |
|
126 | var obj = {"desc": val[1].desc, "count": val[1].count}; | |
127 |
|
127 | |||
128 | // meta language names |
|
128 | // meta language names | |
129 | var td1 = document.createElement('td'); |
|
129 | var td1 = document.createElement('td'); | |
130 | var trending_language_label = document.createElement('div'); |
|
130 | var trending_language_label = document.createElement('div'); | |
131 | trending_language_label.innerHTML = obj.desc; |
|
131 | trending_language_label.innerHTML = obj.desc; | |
132 | td1.appendChild(trending_language_label); |
|
132 | td1.appendChild(trending_language_label); | |
133 |
|
133 | |||
134 | // extensions |
|
134 | // extensions | |
135 | var td2 = document.createElement('td'); |
|
135 | var td2 = document.createElement('td'); | |
136 | var extension = document.createElement('div'); |
|
136 | var extension = document.createElement('div'); | |
137 | extension.innerHTML = ".{0}".format(key) |
|
137 | extension.innerHTML = ".{0}".format(key) | |
138 | td2.appendChild(extension); |
|
138 | td2.appendChild(extension); | |
139 |
|
139 | |||
140 | // number of files |
|
140 | // number of files | |
141 | var td3 = document.createElement('td'); |
|
141 | var td3 = document.createElement('td'); | |
142 | var file_count = document.createElement('div'); |
|
142 | var file_count = document.createElement('div'); | |
143 | var percentage_num = Math.round((obj.count / total * 100), 2); |
|
143 | var percentage_num = Math.round((obj.count / total * 100), 2); | |
144 | var label = _ngettext('file', 'files', obj.count); |
|
144 | var label = _ngettext('file', 'files', obj.count); | |
145 | file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ; |
|
145 | file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ; | |
146 | td3.appendChild(file_count); |
|
146 | td3.appendChild(file_count); | |
147 |
|
147 | |||
148 | // percentage |
|
148 | // percentage | |
149 | var td4 = document.createElement('td'); |
|
149 | var td4 = document.createElement('td'); | |
150 | td4.setAttribute("class", 'trending_language'); |
|
150 | td4.setAttribute("class", 'trending_language'); | |
151 |
|
151 | |||
152 | var percentage = document.createElement('div'); |
|
152 | var percentage = document.createElement('div'); | |
153 | percentage.setAttribute('class', 'lang-bar'); |
|
153 | percentage.setAttribute('class', 'lang-bar'); | |
154 | percentage.innerHTML = " "; |
|
154 | percentage.innerHTML = " "; | |
155 | percentage.style.width = percentage_num + '%'; |
|
155 | percentage.style.width = percentage_num + '%'; | |
156 | td4.appendChild(percentage); |
|
156 | td4.appendChild(percentage); | |
157 |
|
157 | |||
158 | tr.appendChild(td1); |
|
158 | tr.appendChild(td1); | |
159 | tr.appendChild(td2); |
|
159 | tr.appendChild(td2); | |
160 | tr.appendChild(td3); |
|
160 | tr.appendChild(td3); | |
161 | tr.appendChild(td4); |
|
161 | tr.appendChild(td4); | |
162 | tbl.appendChild(tr); |
|
162 | tbl.appendChild(tr); | |
163 |
|
163 | |||
164 | }); |
|
164 | }); | |
165 |
|
165 | |||
166 | $(container).html(tbl); |
|
166 | $(container).html(tbl); | |
167 | $(container).addClass('loaded'); |
|
167 | $(container).addClass('loaded'); | |
168 |
|
168 | |||
169 | $('#code_stats_show_more').on('click', function (e) { |
|
169 | $('#code_stats_show_more').on('click', function (e) { | |
170 | e.preventDefault(); |
|
170 | e.preventDefault(); | |
171 | $('.stats_hidden').each(function (idx) { |
|
171 | $('.stats_hidden').each(function (idx) { | |
172 | $(this).css("display", ""); |
|
172 | $(this).css("display", ""); | |
173 | }); |
|
173 | }); | |
174 | $('#code_stats_show_more').hide(); |
|
174 | $('#code_stats_show_more').hide(); | |
175 | }); |
|
175 | }); | |
176 |
|
176 | |||
177 | }; |
|
177 | }; | |
178 |
|
178 | |||
179 | // returns a node from given html; |
|
179 | // returns a node from given html; | |
180 | var fromHTML = function(html){ |
|
180 | var fromHTML = function(html){ | |
181 | var _html = document.createElement('element'); |
|
181 | var _html = document.createElement('element'); | |
182 | _html.innerHTML = html; |
|
182 | _html.innerHTML = html; | |
183 | return _html; |
|
183 | return _html; | |
184 | }; |
|
184 | }; | |
185 |
|
185 | |||
186 | // Toggle Collapsable Content |
|
186 | // Toggle Collapsable Content | |
187 | function collapsableContent() { |
|
187 | function collapsableContent() { | |
188 |
|
188 | |||
189 | $('.collapsable-content').not('.no-hide').hide(); |
|
189 | $('.collapsable-content').not('.no-hide').hide(); | |
190 |
|
190 | |||
191 | $('.btn-collapse').unbind(); //in case we've been here before |
|
191 | $('.btn-collapse').unbind(); //in case we've been here before | |
192 | $('.btn-collapse').click(function() { |
|
192 | $('.btn-collapse').click(function() { | |
193 | var button = $(this); |
|
193 | var button = $(this); | |
194 | var togglename = $(this).data("toggle"); |
|
194 | var togglename = $(this).data("toggle"); | |
195 | $('.collapsable-content[data-toggle='+togglename+']').toggle(); |
|
195 | $('.collapsable-content[data-toggle='+togglename+']').toggle(); | |
196 | if ($(this).html()=="Show Less") |
|
196 | if ($(this).html()=="Show Less") | |
197 | $(this).html("Show More"); |
|
197 | $(this).html("Show More"); | |
198 | else |
|
198 | else | |
199 | $(this).html("Show Less"); |
|
199 | $(this).html("Show Less"); | |
200 | }); |
|
200 | }); | |
201 | }; |
|
201 | }; | |
202 |
|
202 | |||
203 | var timeagoActivate = function() { |
|
203 | var timeagoActivate = function() { | |
204 | $("time.timeago").timeago(); |
|
204 | $("time.timeago").timeago(); | |
205 | }; |
|
205 | }; | |
206 |
|
206 | |||
207 |
|
207 | |||
208 | var clipboardActivate = function() { |
|
208 | var clipboardActivate = function() { | |
209 | /* |
|
209 | /* | |
210 | * |
|
210 | * | |
211 | * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i> |
|
211 | * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i> | |
212 | * */ |
|
212 | * */ | |
213 | var clipboard = new ClipboardJS('.clipboard-action'); |
|
213 | var clipboard = new ClipboardJS('.clipboard-action'); | |
214 |
|
214 | |||
215 | clipboard.on('success', function(e) { |
|
215 | clipboard.on('success', function(e) { | |
216 | var callback = function () { |
|
216 | var callback = function () { | |
217 | $(e.trigger).animate({'opacity': 1.00}, 200) |
|
217 | $(e.trigger).animate({'opacity': 1.00}, 200) | |
218 | }; |
|
218 | }; | |
219 | $(e.trigger).animate({'opacity': 0.15}, 200, callback); |
|
219 | $(e.trigger).animate({'opacity': 0.15}, 200, callback); | |
220 | e.clearSelection(); |
|
220 | e.clearSelection(); | |
221 | }); |
|
221 | }); | |
222 | }; |
|
222 | }; | |
223 |
|
223 | |||
224 | var tooltipActivate = function () { |
|
224 | var tooltipActivate = function () { | |
225 | var delay = 50; |
|
225 | var delay = 50; | |
226 | var animation = 'fade'; |
|
226 | var animation = 'fade'; | |
227 | var theme = 'tooltipster-shadow'; |
|
227 | var theme = 'tooltipster-shadow'; | |
228 | var debug = false; |
|
228 | var debug = false; | |
229 |
|
229 | |||
230 | $('.tooltip').tooltipster({ |
|
230 | $('.tooltip').tooltipster({ | |
231 | debug: debug, |
|
231 | debug: debug, | |
232 | theme: theme, |
|
232 | theme: theme, | |
233 | animation: animation, |
|
233 | animation: animation, | |
234 | delay: delay, |
|
234 | delay: delay, | |
235 | contentCloning: true, |
|
235 | contentCloning: true, | |
236 | contentAsHTML: true, |
|
236 | contentAsHTML: true, | |
237 |
|
237 | |||
238 | functionBefore: function (instance, helper) { |
|
238 | functionBefore: function (instance, helper) { | |
239 | var $origin = $(helper.origin); |
|
239 | var $origin = $(helper.origin); | |
240 | var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content()); |
|
240 | var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content()); | |
241 | instance.content(data); |
|
241 | instance.content(data); | |
242 | } |
|
242 | } | |
243 | }); |
|
243 | }); | |
244 | var hovercardCache = {}; |
|
244 | var hovercardCache = {}; | |
245 |
|
245 | |||
246 | var loadHoverCard = function (url, altHovercard, callback) { |
|
246 | var loadHoverCard = function (url, altHovercard, callback) { | |
247 | var id = url; |
|
247 | var id = url; | |
248 |
|
248 | |||
249 | if (hovercardCache[id] !== undefined) { |
|
249 | if (hovercardCache[id] !== undefined) { | |
250 | callback(hovercardCache[id]); |
|
250 | callback(hovercardCache[id]); | |
251 | return true; |
|
251 | return true; | |
252 | } |
|
252 | } | |
253 |
|
253 | |||
254 | hovercardCache[id] = undefined; |
|
254 | hovercardCache[id] = undefined; | |
255 | $.get(url, function (data) { |
|
255 | $.get(url, function (data) { | |
256 | hovercardCache[id] = data; |
|
256 | hovercardCache[id] = data; | |
257 | callback(hovercardCache[id]); |
|
257 | callback(hovercardCache[id]); | |
258 | return true; |
|
258 | return true; | |
259 | }).fail(function (data, textStatus, errorThrown) { |
|
259 | }).fail(function (data, textStatus, errorThrown) { | |
260 |
|
260 | |||
261 | if (parseInt(data.status) === 404) { |
|
261 | if (parseInt(data.status) === 404) { | |
262 | var msg = "<p>{0}</p>".format(altHovercard || "No Data exists for this hovercard"); |
|
262 | var msg = "<p>{0}</p>".format(altHovercard || "No Data exists for this hovercard"); | |
263 | } else { |
|
263 | } else { | |
264 | var msg = "<p class='error-message'>Error while fetching hovercard.\nError code {0} ({1}).</p>".format(data.status,data.statusText); |
|
264 | var msg = "<p class='error-message'>Error while fetching hovercard.\nError code {0} ({1}).</p>".format(data.status,data.statusText); | |
265 | } |
|
265 | } | |
266 | callback(msg); |
|
266 | callback(msg); | |
267 | return false |
|
267 | return false | |
268 | }); |
|
268 | }); | |
269 | }; |
|
269 | }; | |
270 |
|
270 | |||
271 | $('.tooltip-hovercard').tooltipster({ |
|
271 | $('.tooltip-hovercard').tooltipster({ | |
272 | debug: debug, |
|
272 | debug: debug, | |
273 | theme: theme, |
|
273 | theme: theme, | |
274 | animation: animation, |
|
274 | animation: animation, | |
275 | delay: delay, |
|
275 | delay: delay, | |
276 | interactive: true, |
|
276 | interactive: true, | |
277 | contentCloning: true, |
|
277 | contentCloning: true, | |
278 |
|
278 | |||
279 | trigger: 'custom', |
|
279 | trigger: 'custom', | |
280 | triggerOpen: { |
|
280 | triggerOpen: { | |
281 | mouseenter: true, |
|
281 | mouseenter: true, | |
282 | }, |
|
282 | }, | |
283 | triggerClose: { |
|
283 | triggerClose: { | |
284 | mouseleave: true, |
|
284 | mouseleave: true, | |
285 | originClick: true, |
|
285 | originClick: true, | |
286 | touchleave: true |
|
286 | touchleave: true | |
287 | }, |
|
287 | }, | |
288 | content: _gettext('Loading...'), |
|
288 | content: _gettext('Loading...'), | |
289 | contentAsHTML: true, |
|
289 | contentAsHTML: true, | |
290 | updateAnimation: null, |
|
290 | updateAnimation: null, | |
291 |
|
291 | |||
292 | functionBefore: function (instance, helper) { |
|
292 | functionBefore: function (instance, helper) { | |
293 |
|
293 | |||
294 | var $origin = $(helper.origin); |
|
294 | var $origin = $(helper.origin); | |
295 |
|
295 | |||
296 | // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens |
|
296 | // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens | |
297 | if ($origin.data('loaded') !== true) { |
|
297 | if ($origin.data('loaded') !== true) { | |
298 | var hovercardUrl = $origin.data('hovercardUrl'); |
|
298 | var hovercardUrl = $origin.data('hovercardUrl'); | |
299 | var altHovercard =$origin.data('hovercardAlt'); |
|
299 | var altHovercard = $origin.data('hovercardAlt'); | |
300 |
|
300 | |||
301 | if (hovercardUrl !== undefined && hovercardUrl !== "") { |
|
301 | if (hovercardUrl !== undefined && hovercardUrl !== "") { | |
|
302 | var urlLoad = true; | |||
302 | if (hovercardUrl.substr(0,12) === 'pyroutes.url'){ |
|
303 | if (hovercardUrl.substr(0, 12) === 'pyroutes.url') { | |
303 | hovercardUrl = eval(hovercardUrl) |
|
304 | hovercardUrl = eval(hovercardUrl) | |
|
305 | } else if (hovercardUrl.substr(0, 11) === 'javascript:') { | |||
|
306 | var jsFunc = hovercardUrl.substr(11); | |||
|
307 | urlLoad = false; | |||
|
308 | loaded = true; | |||
|
309 | instance.content(eval(jsFunc)) | |||
304 | } |
|
310 | } | |
305 |
|
311 | |||
|
312 | if (urlLoad) { | |||
306 | var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) { |
|
313 | var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) { | |
307 | instance.content(data); |
|
314 | instance.content(data); | |
308 | }) |
|
315 | }) | |
|
316 | } | |||
|
317 | ||||
309 | } else { |
|
318 | } else { | |
310 | if ($origin.data('hovercardAltHtml')) { |
|
319 | if ($origin.data('hovercardAltHtml')) { | |
311 | var data = atob($origin.data('hovercardAltHtml')); |
|
320 | var data = atob($origin.data('hovercardAltHtml')); | |
312 | } else { |
|
321 | } else { | |
313 | var data = '<div style="white-space: pre-wrap">{0}</div>'.format(altHovercard) |
|
322 | var data = '<div style="white-space: pre-wrap">{0}</div>'.format(altHovercard) | |
314 | } |
|
323 | } | |
315 | var loaded = true; |
|
324 | var loaded = true; | |
316 | instance.content(data); |
|
325 | instance.content(data); | |
317 | } |
|
326 | } | |
318 |
|
327 | |||
319 | // to remember that the data has been loaded |
|
328 | // to remember that the data has been loaded | |
320 | $origin.data('loaded', loaded); |
|
329 | $origin.data('loaded', loaded); | |
321 | } |
|
330 | } | |
322 | } |
|
331 | } | |
323 | }) |
|
332 | }) | |
324 | }; |
|
333 | }; | |
325 |
|
334 | |||
326 | // Formatting values in a Select2 dropdown of commit references |
|
335 | // Formatting values in a Select2 dropdown of commit references | |
327 | var formatSelect2SelectionRefs = function(commit_ref){ |
|
336 | var formatSelect2SelectionRefs = function(commit_ref){ | |
328 | var tmpl = ''; |
|
337 | var tmpl = ''; | |
329 | if (!commit_ref.text || commit_ref.type === 'sha'){ |
|
338 | if (!commit_ref.text || commit_ref.type === 'sha'){ | |
330 | return commit_ref.text; |
|
339 | return commit_ref.text; | |
331 | } |
|
340 | } | |
332 | if (commit_ref.type === 'branch'){ |
|
341 | if (commit_ref.type === 'branch'){ | |
333 | tmpl = tmpl.concat('<i class="icon-branch"></i> '); |
|
342 | tmpl = tmpl.concat('<i class="icon-branch"></i> '); | |
334 | } else if (commit_ref.type === 'tag'){ |
|
343 | } else if (commit_ref.type === 'tag'){ | |
335 | tmpl = tmpl.concat('<i class="icon-tag"></i> '); |
|
344 | tmpl = tmpl.concat('<i class="icon-tag"></i> '); | |
336 | } else if (commit_ref.type === 'book'){ |
|
345 | } else if (commit_ref.type === 'book'){ | |
337 | tmpl = tmpl.concat('<i class="icon-bookmark"></i> '); |
|
346 | tmpl = tmpl.concat('<i class="icon-bookmark"></i> '); | |
338 | } |
|
347 | } | |
339 | return tmpl.concat(escapeHtml(commit_ref.text)); |
|
348 | return tmpl.concat(escapeHtml(commit_ref.text)); | |
340 | }; |
|
349 | }; | |
341 |
|
350 | |||
342 | // takes a given html element and scrolls it down offset pixels |
|
351 | // takes a given html element and scrolls it down offset pixels | |
343 | function offsetScroll(element, offset) { |
|
352 | function offsetScroll(element, offset) { | |
344 | setTimeout(function() { |
|
353 | setTimeout(function() { | |
345 | var location = element.offset().top; |
|
354 | var location = element.offset().top; | |
346 | // some browsers use body, some use html |
|
355 | // some browsers use body, some use html | |
347 | $('html, body').animate({ scrollTop: (location - offset) }); |
|
356 | $('html, body').animate({ scrollTop: (location - offset) }); | |
348 | }, 100); |
|
357 | }, 100); | |
349 | } |
|
358 | } | |
350 |
|
359 | |||
351 | // scroll an element `percent`% from the top of page in `time` ms |
|
360 | // scroll an element `percent`% from the top of page in `time` ms | |
352 | function scrollToElement(element, percent, time) { |
|
361 | function scrollToElement(element, percent, time) { | |
353 | percent = (percent === undefined ? 25 : percent); |
|
362 | percent = (percent === undefined ? 25 : percent); | |
354 | time = (time === undefined ? 100 : time); |
|
363 | time = (time === undefined ? 100 : time); | |
355 |
|
364 | |||
356 | var $element = $(element); |
|
365 | var $element = $(element); | |
357 | if ($element.length == 0) { |
|
366 | if ($element.length == 0) { | |
358 | throw('Cannot scroll to {0}'.format(element)) |
|
367 | throw('Cannot scroll to {0}'.format(element)) | |
359 | } |
|
368 | } | |
360 | var elOffset = $element.offset().top; |
|
369 | var elOffset = $element.offset().top; | |
361 | var elHeight = $element.height(); |
|
370 | var elHeight = $element.height(); | |
362 | var windowHeight = $(window).height(); |
|
371 | var windowHeight = $(window).height(); | |
363 | var offset = elOffset; |
|
372 | var offset = elOffset; | |
364 | if (elHeight < windowHeight) { |
|
373 | if (elHeight < windowHeight) { | |
365 | offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2)); |
|
374 | offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2)); | |
366 | } |
|
375 | } | |
367 | setTimeout(function() { |
|
376 | setTimeout(function() { | |
368 | $('html, body').animate({ scrollTop: offset}); |
|
377 | $('html, body').animate({ scrollTop: offset}); | |
369 | }, time); |
|
378 | }, time); | |
370 | } |
|
379 | } | |
371 |
|
380 | |||
372 | /** |
|
381 | /** | |
373 | * global hooks after DOM is loaded |
|
382 | * global hooks after DOM is loaded | |
374 | */ |
|
383 | */ | |
375 | $(document).ready(function() { |
|
384 | $(document).ready(function() { | |
376 | firefoxAnchorFix(); |
|
385 | firefoxAnchorFix(); | |
377 |
|
386 | |||
378 | $('.navigation a.menulink').on('click', function(e){ |
|
387 | $('.navigation a.menulink').on('click', function(e){ | |
379 | var menuitem = $(this).parent('li'); |
|
388 | var menuitem = $(this).parent('li'); | |
380 | if (menuitem.hasClass('open')) { |
|
389 | if (menuitem.hasClass('open')) { | |
381 | menuitem.removeClass('open'); |
|
390 | menuitem.removeClass('open'); | |
382 | } else { |
|
391 | } else { | |
383 | menuitem.addClass('open'); |
|
392 | menuitem.addClass('open'); | |
384 | $(document).on('click', function(event) { |
|
393 | $(document).on('click', function(event) { | |
385 | if (!$(event.target).closest(menuitem).length) { |
|
394 | if (!$(event.target).closest(menuitem).length) { | |
386 | menuitem.removeClass('open'); |
|
395 | menuitem.removeClass('open'); | |
387 | } |
|
396 | } | |
388 | }); |
|
397 | }); | |
389 | } |
|
398 | } | |
390 | }); |
|
399 | }); | |
391 |
|
400 | |||
392 | $('body').on('click', '.cb-lineno a', function(event) { |
|
401 | $('body').on('click', '.cb-lineno a', function(event) { | |
393 | function sortNumber(a,b) { |
|
402 | function sortNumber(a,b) { | |
394 | return a - b; |
|
403 | return a - b; | |
395 | } |
|
404 | } | |
396 |
|
405 | |||
397 | var lineNo = $(this).data('lineNo'); |
|
406 | var lineNo = $(this).data('lineNo'); | |
398 | var lineName = $(this).attr('name'); |
|
407 | var lineName = $(this).attr('name'); | |
399 |
|
408 | |||
400 | if (lineNo) { |
|
409 | if (lineNo) { | |
401 | var prevLine = $('.cb-line-selected a').data('lineNo'); |
|
410 | var prevLine = $('.cb-line-selected a').data('lineNo'); | |
402 |
|
411 | |||
403 | // on shift, we do a range selection, if we got previous line |
|
412 | // on shift, we do a range selection, if we got previous line | |
404 | if (event.shiftKey && prevLine !== undefined) { |
|
413 | if (event.shiftKey && prevLine !== undefined) { | |
405 | var prevLine = parseInt(prevLine); |
|
414 | var prevLine = parseInt(prevLine); | |
406 | var nextLine = parseInt(lineNo); |
|
415 | var nextLine = parseInt(lineNo); | |
407 | var pos = [prevLine, nextLine].sort(sortNumber); |
|
416 | var pos = [prevLine, nextLine].sort(sortNumber); | |
408 | var anchor = '#L{0}-{1}'.format(pos[0], pos[1]); |
|
417 | var anchor = '#L{0}-{1}'.format(pos[0], pos[1]); | |
409 |
|
418 | |||
410 | // single click |
|
419 | // single click | |
411 | } else { |
|
420 | } else { | |
412 | var nextLine = parseInt(lineNo); |
|
421 | var nextLine = parseInt(lineNo); | |
413 | var pos = [nextLine, nextLine]; |
|
422 | var pos = [nextLine, nextLine]; | |
414 | var anchor = '#L{0}'.format(pos[0]); |
|
423 | var anchor = '#L{0}'.format(pos[0]); | |
415 |
|
424 | |||
416 | } |
|
425 | } | |
417 | // highlight |
|
426 | // highlight | |
418 | var range = []; |
|
427 | var range = []; | |
419 | for (var i = pos[0]; i <= pos[1]; i++) { |
|
428 | for (var i = pos[0]; i <= pos[1]; i++) { | |
420 | range.push(i); |
|
429 | range.push(i); | |
421 | } |
|
430 | } | |
422 | // clear old selected lines |
|
431 | // clear old selected lines | |
423 | $('.cb-line-selected').removeClass('cb-line-selected'); |
|
432 | $('.cb-line-selected').removeClass('cb-line-selected'); | |
424 |
|
433 | |||
425 | $.each(range, function (i, lineNo) { |
|
434 | $.each(range, function (i, lineNo) { | |
426 | var line_td = $('td.cb-lineno#L' + lineNo); |
|
435 | var line_td = $('td.cb-lineno#L' + lineNo); | |
427 |
|
436 | |||
428 | if (line_td.length) { |
|
437 | if (line_td.length) { | |
429 | line_td.addClass('cb-line-selected'); // line number td |
|
438 | line_td.addClass('cb-line-selected'); // line number td | |
430 | line_td.prev().addClass('cb-line-selected'); // line data |
|
439 | line_td.prev().addClass('cb-line-selected'); // line data | |
431 | line_td.next().addClass('cb-line-selected'); // line content |
|
440 | line_td.next().addClass('cb-line-selected'); // line content | |
432 | } |
|
441 | } | |
433 | }); |
|
442 | }); | |
434 |
|
443 | |||
435 | } else if (lineName !== undefined) { // lineName only occurs in diffs |
|
444 | } else if (lineName !== undefined) { // lineName only occurs in diffs | |
436 | // clear old selected lines |
|
445 | // clear old selected lines | |
437 | $('td.cb-line-selected').removeClass('cb-line-selected'); |
|
446 | $('td.cb-line-selected').removeClass('cb-line-selected'); | |
438 | var anchor = '#{0}'.format(lineName); |
|
447 | var anchor = '#{0}'.format(lineName); | |
439 | var diffmode = templateContext.session_attrs.diffmode || "sideside"; |
|
448 | var diffmode = templateContext.session_attrs.diffmode || "sideside"; | |
440 |
|
449 | |||
441 | if (diffmode === "unified") { |
|
450 | if (diffmode === "unified") { | |
442 | $(this).closest('tr').find('td').addClass('cb-line-selected'); |
|
451 | $(this).closest('tr').find('td').addClass('cb-line-selected'); | |
443 | } else { |
|
452 | } else { | |
444 | var activeTd = $(this).closest('td'); |
|
453 | var activeTd = $(this).closest('td'); | |
445 | activeTd.addClass('cb-line-selected'); |
|
454 | activeTd.addClass('cb-line-selected'); | |
446 | activeTd.next('td').addClass('cb-line-selected'); |
|
455 | activeTd.next('td').addClass('cb-line-selected'); | |
447 | } |
|
456 | } | |
448 |
|
457 | |||
449 | } |
|
458 | } | |
450 |
|
459 | |||
451 | // Replace URL without jumping to it if browser supports. |
|
460 | // Replace URL without jumping to it if browser supports. | |
452 | // Default otherwise |
|
461 | // Default otherwise | |
453 | if (history.pushState && anchor !== undefined) { |
|
462 | if (history.pushState && anchor !== undefined) { | |
454 | var new_location = location.href.rstrip('#'); |
|
463 | var new_location = location.href.rstrip('#'); | |
455 | if (location.hash) { |
|
464 | if (location.hash) { | |
456 | // location without hash |
|
465 | // location without hash | |
457 | new_location = new_location.replace(location.hash, ""); |
|
466 | new_location = new_location.replace(location.hash, ""); | |
458 | } |
|
467 | } | |
459 |
|
468 | |||
460 | // Make new anchor url |
|
469 | // Make new anchor url | |
461 | new_location = new_location + anchor; |
|
470 | new_location = new_location + anchor; | |
462 | history.pushState(true, document.title, new_location); |
|
471 | history.pushState(true, document.title, new_location); | |
463 |
|
472 | |||
464 | return false; |
|
473 | return false; | |
465 | } |
|
474 | } | |
466 |
|
475 | |||
467 | }); |
|
476 | }); | |
468 |
|
477 | |||
469 | $('.collapse_file').on('click', function(e) { |
|
478 | $('.collapse_file').on('click', function(e) { | |
470 | e.stopPropagation(); |
|
479 | e.stopPropagation(); | |
471 | if ($(e.target).is('a')) { return; } |
|
480 | if ($(e.target).is('a')) { return; } | |
472 | var node = $(e.delegateTarget).first(); |
|
481 | var node = $(e.delegateTarget).first(); | |
473 | var icon = $($(node.children().first()).children().first()); |
|
482 | var icon = $($(node.children().first()).children().first()); | |
474 | var id = node.attr('fid'); |
|
483 | var id = node.attr('fid'); | |
475 | var target = $('#'+id); |
|
484 | var target = $('#'+id); | |
476 | var tr = $('#tr_'+id); |
|
485 | var tr = $('#tr_'+id); | |
477 | var diff = $('#diff_'+id); |
|
486 | var diff = $('#diff_'+id); | |
478 | if(node.hasClass('expand_file')){ |
|
487 | if(node.hasClass('expand_file')){ | |
479 | node.removeClass('expand_file'); |
|
488 | node.removeClass('expand_file'); | |
480 | icon.removeClass('expand_file_icon'); |
|
489 | icon.removeClass('expand_file_icon'); | |
481 | node.addClass('collapse_file'); |
|
490 | node.addClass('collapse_file'); | |
482 | icon.addClass('collapse_file_icon'); |
|
491 | icon.addClass('collapse_file_icon'); | |
483 | diff.show(); |
|
492 | diff.show(); | |
484 | tr.show(); |
|
493 | tr.show(); | |
485 | target.show(); |
|
494 | target.show(); | |
486 | } else { |
|
495 | } else { | |
487 | node.removeClass('collapse_file'); |
|
496 | node.removeClass('collapse_file'); | |
488 | icon.removeClass('collapse_file_icon'); |
|
497 | icon.removeClass('collapse_file_icon'); | |
489 | node.addClass('expand_file'); |
|
498 | node.addClass('expand_file'); | |
490 | icon.addClass('expand_file_icon'); |
|
499 | icon.addClass('expand_file_icon'); | |
491 | diff.hide(); |
|
500 | diff.hide(); | |
492 | tr.hide(); |
|
501 | tr.hide(); | |
493 | target.hide(); |
|
502 | target.hide(); | |
494 | } |
|
503 | } | |
495 | }); |
|
504 | }); | |
496 |
|
505 | |||
497 | $('#expand_all_files').click(function() { |
|
506 | $('#expand_all_files').click(function() { | |
498 | $('.expand_file').each(function() { |
|
507 | $('.expand_file').each(function() { | |
499 | var node = $(this); |
|
508 | var node = $(this); | |
500 | var icon = $($(node.children().first()).children().first()); |
|
509 | var icon = $($(node.children().first()).children().first()); | |
501 | var id = $(this).attr('fid'); |
|
510 | var id = $(this).attr('fid'); | |
502 | var target = $('#'+id); |
|
511 | var target = $('#'+id); | |
503 | var tr = $('#tr_'+id); |
|
512 | var tr = $('#tr_'+id); | |
504 | var diff = $('#diff_'+id); |
|
513 | var diff = $('#diff_'+id); | |
505 | node.removeClass('expand_file'); |
|
514 | node.removeClass('expand_file'); | |
506 | icon.removeClass('expand_file_icon'); |
|
515 | icon.removeClass('expand_file_icon'); | |
507 | node.addClass('collapse_file'); |
|
516 | node.addClass('collapse_file'); | |
508 | icon.addClass('collapse_file_icon'); |
|
517 | icon.addClass('collapse_file_icon'); | |
509 | diff.show(); |
|
518 | diff.show(); | |
510 | tr.show(); |
|
519 | tr.show(); | |
511 | target.show(); |
|
520 | target.show(); | |
512 | }); |
|
521 | }); | |
513 | }); |
|
522 | }); | |
514 |
|
523 | |||
515 | $('#collapse_all_files').click(function() { |
|
524 | $('#collapse_all_files').click(function() { | |
516 | $('.collapse_file').each(function() { |
|
525 | $('.collapse_file').each(function() { | |
517 | var node = $(this); |
|
526 | var node = $(this); | |
518 | var icon = $($(node.children().first()).children().first()); |
|
527 | var icon = $($(node.children().first()).children().first()); | |
519 | var id = $(this).attr('fid'); |
|
528 | var id = $(this).attr('fid'); | |
520 | var target = $('#'+id); |
|
529 | var target = $('#'+id); | |
521 | var tr = $('#tr_'+id); |
|
530 | var tr = $('#tr_'+id); | |
522 | var diff = $('#diff_'+id); |
|
531 | var diff = $('#diff_'+id); | |
523 | node.removeClass('collapse_file'); |
|
532 | node.removeClass('collapse_file'); | |
524 | icon.removeClass('collapse_file_icon'); |
|
533 | icon.removeClass('collapse_file_icon'); | |
525 | node.addClass('expand_file'); |
|
534 | node.addClass('expand_file'); | |
526 | icon.addClass('expand_file_icon'); |
|
535 | icon.addClass('expand_file_icon'); | |
527 | diff.hide(); |
|
536 | diff.hide(); | |
528 | tr.hide(); |
|
537 | tr.hide(); | |
529 | target.hide(); |
|
538 | target.hide(); | |
530 | }); |
|
539 | }); | |
531 | }); |
|
540 | }); | |
532 |
|
541 | |||
533 | // Mouse over behavior for comments and line selection |
|
542 | // Mouse over behavior for comments and line selection | |
534 |
|
543 | |||
535 | // Select the line that comes from the url anchor |
|
544 | // Select the line that comes from the url anchor | |
536 | // At the time of development, Chrome didn't seem to support jquery's :target |
|
545 | // At the time of development, Chrome didn't seem to support jquery's :target | |
537 | // element, so I had to scroll manually |
|
546 | // element, so I had to scroll manually | |
538 |
|
547 | |||
539 | if (location.hash) { |
|
548 | if (location.hash) { | |
540 | var result = splitDelimitedHash(location.hash); |
|
549 | var result = splitDelimitedHash(location.hash); | |
541 |
|
550 | |||
542 | var loc = result.loc; |
|
551 | var loc = result.loc; | |
543 |
|
552 | |||
544 | if (loc.length > 1) { |
|
553 | if (loc.length > 1) { | |
545 |
|
554 | |||
546 | var highlightable_line_tds = []; |
|
555 | var highlightable_line_tds = []; | |
547 |
|
556 | |||
548 | // source code line format |
|
557 | // source code line format | |
549 | var page_highlights = loc.substring(loc.indexOf('#') + 1).split('L'); |
|
558 | var page_highlights = loc.substring(loc.indexOf('#') + 1).split('L'); | |
550 |
|
559 | |||
551 | // multi-line HL, for files |
|
560 | // multi-line HL, for files | |
552 | if (page_highlights.length > 1) { |
|
561 | if (page_highlights.length > 1) { | |
553 | var highlight_ranges = page_highlights[1].split(","); |
|
562 | var highlight_ranges = page_highlights[1].split(","); | |
554 | var h_lines = []; |
|
563 | var h_lines = []; | |
555 | for (var pos in highlight_ranges) { |
|
564 | for (var pos in highlight_ranges) { | |
556 | var _range = highlight_ranges[pos].split('-'); |
|
565 | var _range = highlight_ranges[pos].split('-'); | |
557 | if (_range.length === 2) { |
|
566 | if (_range.length === 2) { | |
558 | var start = parseInt(_range[0]); |
|
567 | var start = parseInt(_range[0]); | |
559 | var end = parseInt(_range[1]); |
|
568 | var end = parseInt(_range[1]); | |
560 | if (start < end) { |
|
569 | if (start < end) { | |
561 | for (var i = start; i <= end; i++) { |
|
570 | for (var i = start; i <= end; i++) { | |
562 | h_lines.push(i); |
|
571 | h_lines.push(i); | |
563 | } |
|
572 | } | |
564 | } |
|
573 | } | |
565 | } else { |
|
574 | } else { | |
566 | h_lines.push(parseInt(highlight_ranges[pos])); |
|
575 | h_lines.push(parseInt(highlight_ranges[pos])); | |
567 | } |
|
576 | } | |
568 | } |
|
577 | } | |
569 | for (pos in h_lines) { |
|
578 | for (pos in h_lines) { | |
570 | var line_td = $('td.cb-lineno#L' + h_lines[pos]); |
|
579 | var line_td = $('td.cb-lineno#L' + h_lines[pos]); | |
571 | if (line_td.length) { |
|
580 | if (line_td.length) { | |
572 | highlightable_line_tds.push(line_td); |
|
581 | highlightable_line_tds.push(line_td); | |
573 | } |
|
582 | } | |
574 | } |
|
583 | } | |
575 | } |
|
584 | } | |
576 |
|
585 | |||
577 | // now check a direct id reference of line in diff / pull-request page) |
|
586 | // now check a direct id reference of line in diff / pull-request page) | |
578 | if ($(loc).length > 0 && $(loc).hasClass('cb-lineno')) { |
|
587 | if ($(loc).length > 0 && $(loc).hasClass('cb-lineno')) { | |
579 | highlightable_line_tds.push($(loc)); |
|
588 | highlightable_line_tds.push($(loc)); | |
580 | } |
|
589 | } | |
581 |
|
590 | |||
582 | // mark diff lines as selected |
|
591 | // mark diff lines as selected | |
583 | $.each(highlightable_line_tds, function (i, $td) { |
|
592 | $.each(highlightable_line_tds, function (i, $td) { | |
584 | $td.addClass('cb-line-selected'); // line number td |
|
593 | $td.addClass('cb-line-selected'); // line number td | |
585 | $td.prev().addClass('cb-line-selected'); // line data |
|
594 | $td.prev().addClass('cb-line-selected'); // line data | |
586 | $td.next().addClass('cb-line-selected'); // line content |
|
595 | $td.next().addClass('cb-line-selected'); // line content | |
587 | }); |
|
596 | }); | |
588 |
|
597 | |||
589 | if (highlightable_line_tds.length > 0) { |
|
598 | if (highlightable_line_tds.length > 0) { | |
590 | var $first_line_td = highlightable_line_tds[0]; |
|
599 | var $first_line_td = highlightable_line_tds[0]; | |
591 | scrollToElement($first_line_td); |
|
600 | scrollToElement($first_line_td); | |
592 | $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({ |
|
601 | $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({ | |
593 | td: $first_line_td, |
|
602 | td: $first_line_td, | |
594 | remainder: result.remainder |
|
603 | remainder: result.remainder | |
595 | }); |
|
604 | }); | |
596 | } else { |
|
605 | } else { | |
597 | // case for direct anchor to comments |
|
606 | // case for direct anchor to comments | |
598 | var $line = $(loc); |
|
607 | var $line = $(loc); | |
599 |
|
608 | |||
600 | if ($line.hasClass('comment-general')) { |
|
609 | if ($line.hasClass('comment-general')) { | |
601 | $line.show(); |
|
610 | $line.show(); | |
602 | } else if ($line.hasClass('comment-inline')) { |
|
611 | } else if ($line.hasClass('comment-inline')) { | |
603 | $line.show(); |
|
612 | $line.show(); | |
604 | var $cb = $line.closest('.cb'); |
|
613 | var $cb = $line.closest('.cb'); | |
605 | $cb.removeClass('cb-collapsed') |
|
614 | $cb.removeClass('cb-collapsed') | |
606 | } |
|
615 | } | |
607 | if ($line.length > 0) { |
|
616 | if ($line.length > 0) { | |
608 | $line.addClass('comment-selected-hl'); |
|
617 | $line.addClass('comment-selected-hl'); | |
609 | offsetScroll($line, 70); |
|
618 | offsetScroll($line, 70); | |
610 | } |
|
619 | } | |
611 | if (!$line.hasClass('comment-outdated') && result.remainder === '/ReplyToComment') { |
|
620 | if (!$line.hasClass('comment-outdated') && result.remainder === '/ReplyToComment') { | |
612 | $line.nextAll('.cb-comment-add-button').trigger('click'); |
|
621 | $line.nextAll('.cb-comment-add-button').trigger('click'); | |
613 | } |
|
622 | } | |
614 | } |
|
623 | } | |
615 |
|
624 | |||
616 | } |
|
625 | } | |
617 | } |
|
626 | } | |
618 | collapsableContent(); |
|
627 | collapsableContent(); | |
619 | }); |
|
628 | }); | |
620 |
|
629 | |||
621 | var feedLifetimeOptions = function(query, initialData){ |
|
630 | var feedLifetimeOptions = function(query, initialData){ | |
622 | var data = {results: []}; |
|
631 | var data = {results: []}; | |
623 | var isQuery = typeof query.term !== 'undefined'; |
|
632 | var isQuery = typeof query.term !== 'undefined'; | |
624 |
|
633 | |||
625 | var section = _gettext('Lifetime'); |
|
634 | var section = _gettext('Lifetime'); | |
626 | var children = []; |
|
635 | var children = []; | |
627 |
|
636 | |||
628 | //filter results |
|
637 | //filter results | |
629 | $.each(initialData.results, function(idx, value) { |
|
638 | $.each(initialData.results, function(idx, value) { | |
630 |
|
639 | |||
631 | if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) { |
|
640 | if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) { | |
632 | children.push({ |
|
641 | children.push({ | |
633 | 'id': this.id, |
|
642 | 'id': this.id, | |
634 | 'text': this.text |
|
643 | 'text': this.text | |
635 | }) |
|
644 | }) | |
636 | } |
|
645 | } | |
637 |
|
646 | |||
638 | }); |
|
647 | }); | |
639 | data.results.push({ |
|
648 | data.results.push({ | |
640 | 'text': section, |
|
649 | 'text': section, | |
641 | 'children': children |
|
650 | 'children': children | |
642 | }); |
|
651 | }); | |
643 |
|
652 | |||
644 | if (isQuery) { |
|
653 | if (isQuery) { | |
645 |
|
654 | |||
646 | var now = moment.utc(); |
|
655 | var now = moment.utc(); | |
647 |
|
656 | |||
648 | var parseQuery = function(entry, now){ |
|
657 | var parseQuery = function(entry, now){ | |
649 | var fmt = 'DD/MM/YYYY H:mm'; |
|
658 | var fmt = 'DD/MM/YYYY H:mm'; | |
650 | var parsed = moment.utc(entry, fmt); |
|
659 | var parsed = moment.utc(entry, fmt); | |
651 | var diffInMin = parsed.diff(now, 'minutes'); |
|
660 | var diffInMin = parsed.diff(now, 'minutes'); | |
652 |
|
661 | |||
653 | if (diffInMin > 0){ |
|
662 | if (diffInMin > 0){ | |
654 | return { |
|
663 | return { | |
655 | id: diffInMin, |
|
664 | id: diffInMin, | |
656 | text: parsed.format(fmt) |
|
665 | text: parsed.format(fmt) | |
657 | } |
|
666 | } | |
658 | } else { |
|
667 | } else { | |
659 | return { |
|
668 | return { | |
660 | id: undefined, |
|
669 | id: undefined, | |
661 | text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future') |
|
670 | text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future') | |
662 | } |
|
671 | } | |
663 | } |
|
672 | } | |
664 |
|
673 | |||
665 |
|
674 | |||
666 | }; |
|
675 | }; | |
667 |
|
676 | |||
668 | data.results.push({ |
|
677 | data.results.push({ | |
669 | 'text': _gettext('Specified expiration date'), |
|
678 | 'text': _gettext('Specified expiration date'), | |
670 | 'children': [{ |
|
679 | 'children': [{ | |
671 | 'id': parseQuery(query.term, now).id, |
|
680 | 'id': parseQuery(query.term, now).id, | |
672 | 'text': parseQuery(query.term, now).text |
|
681 | 'text': parseQuery(query.term, now).text | |
673 | }] |
|
682 | }] | |
674 | }); |
|
683 | }); | |
675 | } |
|
684 | } | |
676 |
|
685 | |||
677 | query.callback(data); |
|
686 | query.callback(data); | |
678 | }; |
|
687 | }; | |
679 |
|
688 | |||
680 | /* |
|
689 | /* | |
681 | * Retrievew via templateContext.session_attrs.key |
|
690 | * Retrievew via templateContext.session_attrs.key | |
682 | * */ |
|
691 | * */ | |
683 | var storeUserSessionAttr = function (key, val) { |
|
692 | var storeUserSessionAttr = function (key, val) { | |
684 |
|
693 | |||
685 | var postData = { |
|
694 | var postData = { | |
686 | 'key': key, |
|
695 | 'key': key, | |
687 | 'val': val, |
|
696 | 'val': val, | |
688 | 'csrf_token': CSRF_TOKEN |
|
697 | 'csrf_token': CSRF_TOKEN | |
689 | }; |
|
698 | }; | |
690 |
|
699 | |||
691 | var success = function(o) { |
|
700 | var success = function(o) { | |
692 | return true |
|
701 | return true | |
693 | }; |
|
702 | }; | |
694 |
|
703 | |||
695 | ajaxPOST(pyroutes.url('store_user_session_value'), postData, success); |
|
704 | ajaxPOST(pyroutes.url('store_user_session_value'), postData, success); | |
696 | return false; |
|
705 | return false; | |
697 | }; |
|
706 | }; |
@@ -1,134 +1,140 b'' | |||||
1 | ## snippet for sidebar elements |
|
1 | ## snippet for sidebar elements | |
2 | ## usage: |
|
2 | ## usage: | |
3 | ## <%namespace name="sidebar" file="/base/sidebar.mako"/> |
|
3 | ## <%namespace name="sidebar" file="/base/sidebar.mako"/> | |
4 | ## ${sidebar.comments_table()} |
|
4 | ## ${sidebar.comments_table()} | |
5 | <%namespace name="base" file="/base/base.mako"/> |
|
5 | <%namespace name="base" file="/base/base.mako"/> | |
6 |
|
6 | |||
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)"> |
|
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)"> | |
8 | <% |
|
8 | <% | |
9 | if todo_comments: |
|
9 | if todo_comments: | |
10 | cls_ = 'todos-content-table' |
|
10 | cls_ = 'todos-content-table' | |
11 | def sorter(entry): |
|
11 | def sorter(entry): | |
12 | user_id = entry.author.user_id |
|
12 | user_id = entry.author.user_id | |
13 | resolved = '1' if entry.resolved else '0' |
|
13 | resolved = '1' if entry.resolved else '0' | |
14 | if user_id == c.rhodecode_user.user_id: |
|
14 | if user_id == c.rhodecode_user.user_id: | |
15 | # own comments first |
|
15 | # own comments first | |
16 | user_id = 0 |
|
16 | user_id = 0 | |
17 | return '{}'.format(str(entry.comment_id).zfill(10000)) |
|
17 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |
18 | else: |
|
18 | else: | |
19 | cls_ = 'comments-content-table' |
|
19 | cls_ = 'comments-content-table' | |
20 | def sorter(entry): |
|
20 | def sorter(entry): | |
21 | user_id = entry.author.user_id |
|
21 | user_id = entry.author.user_id | |
22 | return '{}'.format(str(entry.comment_id).zfill(10000)) |
|
22 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |
23 |
|
23 | |||
24 | existing_ids = existing_ids or [] |
|
24 | existing_ids = existing_ids or [] | |
25 |
|
25 | |||
26 | %> |
|
26 | %> | |
27 |
|
27 | |||
28 | <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}"> |
|
28 | <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}"> | |
29 |
|
29 | |||
30 | % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))): |
|
30 | % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))): | |
31 | <% |
|
31 | <% | |
32 | display = '' |
|
32 | display = '' | |
33 | _cls = '' |
|
33 | _cls = '' | |
34 | %> |
|
34 | %> | |
35 |
|
35 | |||
36 | <% |
|
36 | <% | |
37 | comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) |
|
37 | comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) | |
38 | prev_comment_ver_index = 0 |
|
38 | prev_comment_ver_index = 0 | |
39 | if loop_obj.previous: |
|
39 | if loop_obj.previous: | |
40 | prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', [])) |
|
40 | prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', [])) | |
41 |
|
41 | |||
42 | ver_info = None |
|
42 | ver_info = None | |
43 | if getattr(c, 'versions', []): |
|
43 | if getattr(c, 'versions', []): | |
44 | ver_info = c.versions[comment_ver_index-1] if comment_ver_index else None |
|
44 | ver_info = c.versions[comment_ver_index-1] if comment_ver_index else None | |
45 | %> |
|
45 | %> | |
46 | <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %> |
|
46 | <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %> | |
47 | <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %> |
|
47 | <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %> | |
48 | <% |
|
48 | <% | |
49 | if (prev_comment_ver_index > comment_ver_index): |
|
49 | if (prev_comment_ver_index > comment_ver_index): | |
50 | comments_ver_divider = comment_ver_index |
|
50 | comments_ver_divider = comment_ver_index | |
51 | else: |
|
51 | else: | |
52 | comments_ver_divider = None |
|
52 | comments_ver_divider = None | |
53 | %> |
|
53 | %> | |
54 |
|
54 | |||
55 | % if todo_comments: |
|
55 | % if todo_comments: | |
56 | % if comment_obj.resolved: |
|
56 | % if comment_obj.resolved: | |
57 | <% _cls = 'resolved-todo' %> |
|
57 | <% _cls = 'resolved-todo' %> | |
58 | <% display = 'none' %> |
|
58 | <% display = 'none' %> | |
59 | % endif |
|
59 | % endif | |
60 | % else: |
|
60 | % else: | |
61 | ## SKIP TODOs we display them in other area |
|
61 | ## SKIP TODOs we display them in other area | |
62 | % if comment_obj.is_todo: |
|
62 | % if comment_obj.is_todo: | |
63 | <% display = 'none' %> |
|
63 | <% display = 'none' %> | |
64 | % endif |
|
64 | % endif | |
65 | ## Skip outdated comments |
|
65 | ## Skip outdated comments | |
66 | % if comment_obj.outdated: |
|
66 | % if comment_obj.outdated: | |
67 | <% display = 'none' %> |
|
67 | <% display = 'none' %> | |
68 | <% _cls = 'hidden-comment' %> |
|
68 | <% _cls = 'hidden-comment' %> | |
69 | % endif |
|
69 | % endif | |
70 | % endif |
|
70 | % endif | |
71 |
|
71 | |||
72 | % if not todo_comments and comments_ver_divider: |
|
72 | % if not todo_comments and comments_ver_divider: | |
73 | <tr class="old-comments-marker"> |
|
73 | <tr class="old-comments-marker"> | |
74 | <td colspan="3"> |
|
74 | <td colspan="3"> | |
75 | % if ver_info: |
|
75 | % if ver_info: | |
76 | <code>v${comments_ver_divider} ${h.age_component(ver_info.created_on, time_is_local=True, tooltip=False)}</code> |
|
76 | <code>v${comments_ver_divider} ${h.age_component(ver_info.created_on, time_is_local=True, tooltip=False)}</code> | |
77 | % else: |
|
77 | % else: | |
78 | <code>v${comments_ver_divider}</code> |
|
78 | <code>v${comments_ver_divider}</code> | |
79 | % endif |
|
79 | % endif | |
80 | </td> |
|
80 | </td> | |
81 | </tr> |
|
81 | </tr> | |
82 |
|
82 | |||
83 | % endif |
|
83 | % endif | |
84 |
|
84 | |||
85 | <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}"> |
|
85 | <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}"> | |
86 | <td class="td-todo-number"> |
|
86 | <td class="td-todo-number"> | |
87 |
|
||||
88 | <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink" |
|
|||
89 | href="#comment-${comment_obj.comment_id}" |
|
|||
90 | onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})"> |
|
|||
91 |
|
||||
92 | <% |
|
87 | <% | |
93 | version_info = '' |
|
88 | version_info = '' | |
94 | if is_pr: |
|
89 | if is_pr: | |
95 | version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version') |
|
90 | version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version') | |
96 | %> |
|
91 | %> | |
|
92 | <script type="text/javascript"> | |||
|
93 | // closure function helper | |||
|
94 | var sidebarComment${comment_obj.comment_id} = function() { | |||
|
95 | return renderTemplate('sideBarCommentHovercard', { | |||
|
96 | version_info: "${version_info}", | |||
|
97 | file_name: "${comment_obj.f_path}", | |||
|
98 | line_no: "${comment_obj.line_no}", | |||
|
99 | outdated: ${h.json.dumps(comment_obj.outdated)}, | |||
|
100 | inline: ${h.json.dumps(comment_obj.is_inline)}, | |||
|
101 | is_todo: ${h.json.dumps(comment_obj.is_todo)}, | |||
|
102 | created_on: "${h.format_date(comment_obj.created_on)}", | |||
|
103 | datetime: "${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}", | |||
|
104 | }) | |||
|
105 | } | |||
|
106 | </script> | |||
97 |
|
107 | |||
98 |
% if |
|
108 | % if comment_obj.outdated: | |
99 | % if comment_obj.is_inline: |
|
109 | <i class="icon-comment-toggle tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
100 | <i class="tooltip icon-code" title="Inline TODO comment${version_info}."></i> |
|
110 | % elif comment_obj.is_inline: | |
101 | % else: |
|
111 | <i class="icon-code tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
102 | <i class="tooltip icon-comment" title="General TODO comment${version_info}."></i> |
|
|||
103 | % endif |
|
|||
104 | % else: |
|
112 | % else: | |
105 | % if comment_obj.outdated: |
|
113 | <i class="icon-comment tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
106 | <i class="tooltip icon-comment-toggle" title="Inline Outdated made in v${comment_ver_index}."></i> |
|
|||
107 | % elif comment_obj.is_inline: |
|
|||
108 | <i class="tooltip icon-code" title="Inline comment${version_info}."></i> |
|
|||
109 | % else: |
|
|||
110 | <i class="tooltip icon-comment" title="General comment${version_info}."></i> |
|
|||
111 | % endif |
|
|||
112 | % endif |
|
114 | % endif | |
113 |
|
115 | |||
114 | </a> |
|
|||
115 | ## NEW, since refresh |
|
116 | ## NEW, since refresh | |
116 | % if existing_ids and comment_obj.comment_id not in existing_ids: |
|
117 | % if existing_ids and comment_obj.comment_id not in existing_ids: | |
117 | <span class="tag">NEW</span> |
|
118 | <span class="tag">NEW</span> | |
118 | % endif |
|
119 | % endif | |
119 | </td> |
|
120 | </td> | |
120 |
|
121 | |||
121 | <td class="td-todo-gravatar"> |
|
122 | <td class="td-todo-gravatar"> | |
122 | ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])} |
|
123 | ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])} | |
123 | </td> |
|
124 | </td> | |
124 | <td class="todo-comment-text-wrapper"> |
|
125 | <td class="todo-comment-text-wrapper"> | |
125 | <div class="tooltip todo-comment-text timeago ${('todo-resolved' if comment_obj.resolved else '')} " title="${h.format_date(comment_obj.created_on)}" datetime="${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}"> |
|
126 | <div class="todo-comment-text ${('todo-resolved' if comment_obj.resolved else '')}"> | |
126 | <code>${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}</code> |
|
127 | <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink" | |
|
128 | href="#comment-${comment_obj.comment_id}" | |||
|
129 | onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})"> | |||
|
130 | ||||
|
131 | ${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')} | |||
|
132 | </a> | |||
127 | </div> |
|
133 | </div> | |
128 | </td> |
|
134 | </td> | |
129 | </tr> |
|
135 | </tr> | |
130 | % endfor |
|
136 | % endfor | |
131 |
|
137 | |||
132 | </table> |
|
138 | </table> | |
133 |
|
139 | |||
134 | </%def> No newline at end of file |
|
140 | </%def> |
@@ -1,193 +1,236 b'' | |||||
1 | <%text> |
|
1 | <%text> | |
2 | <div style="display: none"> |
|
2 | <div style="display: none"> | |
3 |
|
3 | |||
4 | <script> |
|
4 | <script> | |
5 | var CG = new ColorGenerator(); |
|
5 | var CG = new ColorGenerator(); | |
6 | </script> |
|
6 | </script> | |
7 |
|
7 | |||
8 | <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate"> |
|
8 | <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate"> | |
9 |
|
9 | |||
10 | <% |
|
10 | <% | |
11 | if (size > 16) { |
|
11 | if (size > 16) { | |
12 | var gravatar_class = 'gravatar gravatar-large'; |
|
12 | var gravatar_class = 'gravatar gravatar-large'; | |
13 | } else { |
|
13 | } else { | |
14 | var gravatar_class = 'gravatar'; |
|
14 | var gravatar_class = 'gravatar'; | |
15 | } |
|
15 | } | |
16 |
|
16 | |||
17 | if (tooltip) { |
|
17 | if (tooltip) { | |
18 | var gravatar_class = gravatar_class + ' tooltip-hovercard'; |
|
18 | var gravatar_class = gravatar_class + ' tooltip-hovercard'; | |
19 | } |
|
19 | } | |
20 |
|
20 | |||
21 | var data_hovercard_alt = username; |
|
21 | var data_hovercard_alt = username; | |
22 |
|
22 | |||
23 | %> |
|
23 | %> | |
24 |
|
24 | |||
25 | <% |
|
25 | <% | |
26 | if (show_disabled) { |
|
26 | if (show_disabled) { | |
27 | var user_cls = 'user user-disabled'; |
|
27 | var user_cls = 'user user-disabled'; | |
28 | } else { |
|
28 | } else { | |
29 | var user_cls = 'user'; |
|
29 | var user_cls = 'user'; | |
30 | } |
|
30 | } | |
31 | var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id}) |
|
31 | var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id}) | |
32 | %> |
|
32 | %> | |
33 |
|
33 | |||
34 | <div class="rc-user"> |
|
34 | <div class="rc-user"> | |
35 | <img class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" data-hovercard-url="<%= data_hovercard_url %>" data-hovercard-alt="<%= data_hovercard_alt %>" src="<%- gravatar_url -%>"> |
|
35 | <img class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" data-hovercard-url="<%= data_hovercard_url %>" data-hovercard-alt="<%= data_hovercard_alt %>" src="<%- gravatar_url -%>"> | |
36 | <span class="<%= user_cls %>"> <%- user_link -%> </span> |
|
36 | <span class="<%= user_cls %>"> <%- user_link -%> </span> | |
37 | </div> |
|
37 | </div> | |
38 |
|
38 | |||
39 | </script> |
|
39 | </script> | |
40 |
|
40 | |||
41 | <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate"> |
|
41 | <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate"> | |
42 | <% |
|
42 | <% | |
43 | if (create) { |
|
43 | if (create) { | |
44 | var edit_visibility = 'visible'; |
|
44 | var edit_visibility = 'visible'; | |
45 | } else { |
|
45 | } else { | |
46 | var edit_visibility = 'hidden'; |
|
46 | var edit_visibility = 'hidden'; | |
47 | } |
|
47 | } | |
48 |
|
48 | |||
49 | if (member.user_group && member.user_group.vote_rule) { |
|
49 | if (member.user_group && member.user_group.vote_rule) { | |
50 | var reviewGroup = '<i class="icon-user-group"></i>'; |
|
50 | var reviewGroup = '<i class="icon-user-group"></i>'; | |
51 | var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule)); |
|
51 | var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule)); | |
52 | } else { |
|
52 | } else { | |
53 | var reviewGroup = null; |
|
53 | var reviewGroup = null; | |
54 | var reviewGroupColor = 'transparent'; |
|
54 | var reviewGroupColor = 'transparent'; | |
55 | } |
|
55 | } | |
56 | var rule_show = rule_show || false; |
|
56 | var rule_show = rule_show || false; | |
57 |
|
57 | |||
58 | if (rule_show) { |
|
58 | if (rule_show) { | |
59 | var rule_visibility = 'table-cell'; |
|
59 | var rule_visibility = 'table-cell'; | |
60 | } else { |
|
60 | } else { | |
61 | var rule_visibility = 'none'; |
|
61 | var rule_visibility = 'none'; | |
62 | } |
|
62 | } | |
63 |
|
63 | |||
64 | %> |
|
64 | %> | |
65 |
|
65 | |||
66 | <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>"> |
|
66 | <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>"> | |
67 |
|
67 | |||
68 | <td style="width: 20px"> |
|
68 | <td style="width: 20px"> | |
69 | <div class="reviewer_status tooltip" title="<%= review_status_label %>"> |
|
69 | <div class="reviewer_status tooltip" title="<%= review_status_label %>"> | |
70 | <i class="icon-circle review-status-<%= review_status %>"></i> |
|
70 | <i class="icon-circle review-status-<%= review_status %>"></i> | |
71 | </div> |
|
71 | </div> | |
72 | </td> |
|
72 | </td> | |
73 |
|
73 | |||
74 | <td> |
|
74 | <td> | |
75 | <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name"> |
|
75 | <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name"> | |
76 | <%- |
|
76 | <%- | |
77 | renderTemplate('gravatarWithUser', { |
|
77 | renderTemplate('gravatarWithUser', { | |
78 | 'size': 16, |
|
78 | 'size': 16, | |
79 | 'show_disabled': false, |
|
79 | 'show_disabled': false, | |
80 | 'tooltip': true, |
|
80 | 'tooltip': true, | |
81 | 'username': member.username, |
|
81 | 'username': member.username, | |
82 | 'user_id': member.user_id, |
|
82 | 'user_id': member.user_id, | |
83 | 'user_link': member.user_link, |
|
83 | 'user_link': member.user_link, | |
84 | 'gravatar_url': member.gravatar_link |
|
84 | 'gravatar_url': member.gravatar_link | |
85 | }) |
|
85 | }) | |
86 | %> |
|
86 | %> | |
87 | <span class="tooltip presence-state" style="display: none" title="This users is currently at this page"> |
|
87 | <span class="tooltip presence-state" style="display: none" title="This users is currently at this page"> | |
88 | <i class="icon-eye" style="color: #0ac878"></i> |
|
88 | <i class="icon-eye" style="color: #0ac878"></i> | |
89 | </span> |
|
89 | </span> | |
90 | </div> |
|
90 | </div> | |
91 | </td> |
|
91 | </td> | |
92 |
|
92 | |||
93 | <td style="width: 10px"> |
|
93 | <td style="width: 10px"> | |
94 | <% if (reviewGroup !== null) { %> |
|
94 | <% if (reviewGroup !== null) { %> | |
95 | <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>"> |
|
95 | <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>"> | |
96 | <%- reviewGroup %> |
|
96 | <%- reviewGroup %> | |
97 | </span> |
|
97 | </span> | |
98 | <% } %> |
|
98 | <% } %> | |
99 | </td> |
|
99 | </td> | |
100 |
|
100 | |||
101 | <% if (mandatory) { %> |
|
101 | <% if (mandatory) { %> | |
102 | <td style="text-align: right;width: 10px;"> |
|
102 | <td style="text-align: right;width: 10px;"> | |
103 | <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer"> |
|
103 | <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer"> | |
104 | <i class="icon-lock"></i> |
|
104 | <i class="icon-lock"></i> | |
105 | </div> |
|
105 | </div> | |
106 | </td> |
|
106 | </td> | |
107 |
|
107 | |||
108 | <% } else { %> |
|
108 | <% } else { %> | |
109 | <td style="text-align: right;width: 10px;"> |
|
109 | <td style="text-align: right;width: 10px;"> | |
110 | <% if (allowed_to_update) { %> |
|
110 | <% if (allowed_to_update) { %> | |
111 | <div class="reviewer_member_remove" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;"> |
|
111 | <div class="reviewer_member_remove" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;"> | |
112 | <i class="icon-remove"></i> |
|
112 | <i class="icon-remove"></i> | |
113 | </div> |
|
113 | </div> | |
114 | <% } %> |
|
114 | <% } %> | |
115 | </td> |
|
115 | </td> | |
116 | <% } %> |
|
116 | <% } %> | |
117 |
|
117 | |||
118 | </tr> |
|
118 | </tr> | |
119 |
|
119 | |||
120 | <tr> |
|
120 | <tr> | |
121 | <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container"> |
|
121 | <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container"> | |
122 | <input type="hidden" name="__start__" value="reviewer:mapping"> |
|
122 | <input type="hidden" name="__start__" value="reviewer:mapping"> | |
123 |
|
123 | |||
124 | <%if (member.user_group && member.user_group.vote_rule) { %> |
|
124 | <%if (member.user_group && member.user_group.vote_rule) { %> | |
125 | <div class="reviewer_reason"> |
|
125 | <div class="reviewer_reason"> | |
126 |
|
126 | |||
127 | <%if (member.user_group.vote_rule == -1) {%> |
|
127 | <%if (member.user_group.vote_rule == -1) {%> | |
128 | - group votes required: ALL |
|
128 | - group votes required: ALL | |
129 | <%} else {%> |
|
129 | <%} else {%> | |
130 | - group votes required: <%= member.user_group.vote_rule %> |
|
130 | - group votes required: <%= member.user_group.vote_rule %> | |
131 | <%}%> |
|
131 | <%}%> | |
132 | </div> |
|
132 | </div> | |
133 | <%} %> |
|
133 | <%} %> | |
134 |
|
134 | |||
135 | <input type="hidden" name="__start__" value="reasons:sequence"> |
|
135 | <input type="hidden" name="__start__" value="reasons:sequence"> | |
136 | <% for (var i = 0; i < reasons.length; i++) { %> |
|
136 | <% for (var i = 0; i < reasons.length; i++) { %> | |
137 | <% var reason = reasons[i] %> |
|
137 | <% var reason = reasons[i] %> | |
138 | <div class="reviewer_reason">- <%= reason %></div> |
|
138 | <div class="reviewer_reason">- <%= reason %></div> | |
139 | <input type="hidden" name="reason" value="<%= reason %>"> |
|
139 | <input type="hidden" name="reason" value="<%= reason %>"> | |
140 | <% } %> |
|
140 | <% } %> | |
141 | <input type="hidden" name="__end__" value="reasons:sequence"> |
|
141 | <input type="hidden" name="__end__" value="reasons:sequence"> | |
142 |
|
142 | |||
143 | <input type="hidden" name="__start__" value="rules:sequence"> |
|
143 | <input type="hidden" name="__start__" value="rules:sequence"> | |
144 | <% for (var i = 0; i < member.rules.length; i++) { %> |
|
144 | <% for (var i = 0; i < member.rules.length; i++) { %> | |
145 | <% var rule = member.rules[i] %> |
|
145 | <% var rule = member.rules[i] %> | |
146 | <input type="hidden" name="rule_id" value="<%= rule %>"> |
|
146 | <input type="hidden" name="rule_id" value="<%= rule %>"> | |
147 | <% } %> |
|
147 | <% } %> | |
148 | <input type="hidden" name="__end__" value="rules:sequence"> |
|
148 | <input type="hidden" name="__end__" value="rules:sequence"> | |
149 |
|
149 | |||
150 | <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" /> |
|
150 | <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" /> | |
151 | <input type="hidden" name="mandatory" value="<%= mandatory %>"/> |
|
151 | <input type="hidden" name="mandatory" value="<%= mandatory %>"/> | |
152 |
|
152 | |||
153 | <input type="hidden" name="__end__" value="reviewer:mapping"> |
|
153 | <input type="hidden" name="__end__" value="reviewer:mapping"> | |
154 | </td> |
|
154 | </td> | |
155 | </tr> |
|
155 | </tr> | |
156 |
|
156 | |||
157 | </script> |
|
157 | </script> | |
158 |
|
158 | |||
159 | <script id="ejs_commentVersion" type="text/template" class="ejsTemplate"> |
|
159 | <script id="ejs_commentVersion" type="text/template" class="ejsTemplate"> | |
160 |
|
160 | |||
161 | <% |
|
161 | <% | |
162 | if (size > 16) { |
|
162 | if (size > 16) { | |
163 | var gravatar_class = 'gravatar gravatar-large'; |
|
163 | var gravatar_class = 'gravatar gravatar-large'; | |
164 | } else { |
|
164 | } else { | |
165 | var gravatar_class = 'gravatar'; |
|
165 | var gravatar_class = 'gravatar'; | |
166 | } |
|
166 | } | |
167 |
|
167 | |||
168 | %> |
|
168 | %> | |
169 |
|
169 | |||
170 | <% |
|
170 | <% | |
171 | if (show_disabled) { |
|
171 | if (show_disabled) { | |
172 | var user_cls = 'user user-disabled'; |
|
172 | var user_cls = 'user user-disabled'; | |
173 | } else { |
|
173 | } else { | |
174 | var user_cls = 'user'; |
|
174 | var user_cls = 'user'; | |
175 | } |
|
175 | } | |
176 |
|
176 | |||
177 | %> |
|
177 | %> | |
178 |
|
178 | |||
179 | <div style='line-height: 20px'> |
|
179 | <div style='line-height: 20px'> | |
180 | <img style="margin: -3px 0" class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" src="<%- gravatar_url -%>"> |
|
180 | <img style="margin: -3px 0" class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" src="<%- gravatar_url -%>"> | |
181 | <strong><%- user_name -%></strong>, <code>v<%- version -%></code> edited <%- timeago_component -%> |
|
181 | <strong><%- user_name -%></strong>, <code>v<%- version -%></code> edited <%- timeago_component -%> | |
182 | </div> |
|
182 | </div> | |
183 |
|
183 | |||
184 | </script> |
|
184 | </script> | |
185 |
|
185 | |||
|
186 | ||||
|
187 | <script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate"> | |||
|
188 | ||||
|
189 | <div> | |||
|
190 | <% if (is_todo) { %> | |||
|
191 | <% if (inline) { %> | |||
|
192 | <strong>Inline</strong> TODO on line: <%= line_no %> | |||
|
193 | <% if (version_info) { %> | |||
|
194 | <%= version_info %> | |||
|
195 | <% } %> | |||
|
196 | <br/> | |||
|
197 | File: <code><%- file_name -%></code> | |||
|
198 | <% } else { %> | |||
|
199 | <strong>General</strong> TODO | |||
|
200 | <% if (version_info) { %> | |||
|
201 | <%= version_info %> | |||
|
202 | <% } %> | |||
|
203 | <% } %> | |||
|
204 | <% } else { %> | |||
|
205 | <% if (inline) { %> | |||
|
206 | <strong>Inline</strong> comment on line: <%= line_no %> | |||
|
207 | <% if (version_info) { %> | |||
|
208 | <%= version_info %> | |||
|
209 | <% } %> | |||
|
210 | <br/> | |||
|
211 | File: <code><%- file_name -%></code> | |||
|
212 | <% } else { %> | |||
|
213 | <strong>General</strong> comment | |||
|
214 | <% if (version_info) { %> | |||
|
215 | <%= version_info %> | |||
|
216 | <% } %> | |||
|
217 | <% } %> | |||
|
218 | <% } %> | |||
|
219 | <br/> | |||
|
220 | Created: | |||
|
221 | <time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time> | |||
|
222 | ||||
186 | </div> |
|
223 | </div> | |
187 |
|
224 | |||
|
225 | </script> | |||
|
226 | ||||
|
227 | ##// END OF EJS Templates | |||
|
228 | </div> | |||
|
229 | ||||
|
230 | ||||
188 | <script> |
|
231 | <script> | |
189 | // registers the templates into global cache |
|
232 | // registers the templates into global cache | |
190 | registerTemplates(); |
|
233 | registerTemplates(); | |
191 | </script> |
|
234 | </script> | |
192 |
|
235 | |||
193 | </%text> |
|
236 | </%text> |
General Comments 0
You need to be logged in to leave comments.
Login now