Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(682)

Side by Side Diff: reviewbot/handlers/policy_checklist.py

Issue 20518002: Implement mail dispatcher app. (Closed) Base URL: https://src.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import re
6
7 import model.app_config
8 import util
9
10
11 POLICY_TEMPLATES_FILE = 'chrome/app/policy/policy_templates.json'
12 ID_LINE_COMMENT = 'For your editing convenience: highest ID currently used:'
13 CONTEXT_THRESHOLD = 12
iannucci 2013/08/15 03:48:05 Should we make a separate config file specifically
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 I'd expect that if we need changes, not only the c
14 PROPERTY_NAME_RE = re.compile('\'(\\w+)\'\\s*:')
iannucci 2013/08/15 03:48:05 may be worth using a raw string to reduce the back
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 Good idea, done.
15 MAX_LINE = 1000000
16
17 REVIEW_MESSAGE = '''
18 Thanks for improving Chromium enterprise features! In general,
19 enterprise policies should work across all platforms and need to remain
20 backward compatible across several Chrome versions. To help avoid common
21 pitfalls, policy review bot has analyzed your change and added comments
22 with checklists of things to watch out for.
23
24 Please direct comments and complaints about this service to
25 chrome-enterprise@google.com and/or mnissler@chromium.org.
26 '''
27
28 ADDITION_COMMENT = '''
29 When adding a new policy setting, please consider the following:
30
31 - Ideally, your new policy should be expressing the intention of an
32 administrator and/or cover a well-scoped user-visible behavior of the
33 browser as opposed to an implementation-specific parameter. Consider
34 that your implementation may change in the future, and you might have
35 to re-implement your policy on top of the new implementation.
iannucci 2013/08/15 03:48:05 I'm assuming these strings have all been vetted by
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 Yes, I've discussed them in a doc with the chrome/
36 - Make sure your policy definition has proper supported_on declarations
37 specifying the platform and versions this policy is supported on.
38 - Make sure feature flags are correct. In particular:
39 - dynamic_refresh - whether your feature can adjust to changes in
40 policy at run time. You typically use PrefChangeRegistrar to do so.
41 - per_profile - whether your policy is browser-global or can be set
42 independently for each Chrome Profile. This is usually true if you
43 read the policy value from the Profile's PrefService and false if
44 you read it from the local_state PrefService.
45 - can_be_recommended - whether your feature supports admin-supplied
46 default values that the user can override.
47 - Make sure you put a helpful policy description:
48 - Describe the effect of setting the policy in a way that makes sense
49 to somebody who is not intimately familiar with your feature, such
50 as administrators and other Chromium developers.
51 - Do mention behavior for the cases where the policy gets ignored
52 (i.e. not configured, booleans only one value is effective). It's
53 nice for completeness and admins have been asking specifically for
54 this piece of information in the past.
55 - Write a browser_test for you new policy. The ideal test would fire up
56 the browser with the policy set and check whether the policy affects
57 user-visible behavior in the intended way. See
58 chrome/browser/policy/policy_browsertest.cc for examples.
59 - If you're adding a device policy for Chrome OS, be sure to update
60 chrome/browser/chromeos/policy/device_policy_decoder_chromeos.{h,cc}
61 so the policy shows up on the chrome://policy page.
iannucci 2013/08/15 03:48:05 It may make sense to actually turn these strings i
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 Good point. Done.
62 '''
63
64 MODIFICATION_COMMENT = '''
65 When modifying an existing policy setting, please consider the following:
66
67 - Make sure the policy meta data is up-to-date, in particular
68 supported_on, and the feature flags.
69 - In general, don't change policy semantics in a way that is
70 incompatible (as determined by user/admin-visible behavior) with
71 previous semantics. In particular, consider that existing policy
72 deployments may affect both old and new browser versions, and both
73 should behave according to the admin's intentions.
74 - It is OK to expand semantics of policy values as long as the previous
75 policy description is compatible with the new behavior.
76 - It is OK to update feature implementations and the policy description
77 when Chrome changes as long as the intended effect of the policy
78 remains intact.
79 - The process for removing policies is to deprecate them first, wait a
80 few releases (if possible) and then remove them. Make sure you put the
81 deprecated flag if you deprecate a policy.
82 '''
83
84
85 def nmin(*args):
86 """Calculates the minimum of |args|, ignoring None entries."""
87 try:
88 return min(v for v in args if v is not None)
89 except ValueError:
iannucci 2013/08/15 03:48:05 This seems sketchy. Why ValueError? Also, I think
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 min raises ValueError if the sequence is empty. I'
90 return None
91
92
93 def nmax(*args):
94 """Calculates the maximum of |args|, ignoring None entries."""
95 try:
96 return max(v for v in args if v is not None)
97 except ValueError:
98 return None
99
100
101 def nsub(a, b):
102 """Calculates a - b, returning None if either a or b is None"""
103 return None if (a is None or b is None) else a - b
104
105
106 def indent(line):
107 """Returns the indent level (number of leading spaces) for |line|."""
108 nspaces = len(line) - len(line.lstrip(' '))
109 return None if nspaces == 0 else nspaces
iannucci 2013/08/15 03:48:05 You may want to take a look at the textwrap module
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 This actually determines the indent from a given l
110
111
112 class PolicyChangeParser(object):
iannucci 2013/08/15 03:48:05 Needs docs, code is hard to read without context/e
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 I've added documentation and some illustrative uni
113 def __init__(self, lines):
114 self.lines = lines
115 self.chunks_list = []
116 self.reset()
117
118 def run(self):
119 self.chunks_list = []
120 self.last_change = [None, None]
121 cursor = [None, None]
122 self.reset()
123 for (a_line, b_line, line) in self.lines:
124 # Skip comment lines.
125 if line.startswith('#'):
126 continue
127
128 # See whether the current line has a JSON property.
129 keyword = None
130 match = PROPERTY_NAME_RE.search(line)
131 if match:
132 keyword = match.group(1).lower()
133
134
135 # Check whether the current block closes.
136 line_indent = indent(line)
137 if (self.block_indent is not None and
138 line_indent is not None and
139 line_indent < self.block_indent):
140 self.block_closed = True
141
142 if (keyword == 'name' and self.block_closed):
143 # If we see the 'name' property, that likely indicates the start of a
144 # new policy. Start a new chunk.
145 self.flush_chunk()
146
147 # Update various cursors.
148 cursor = [nmax(a_line, cursor[0]), nmax(b_line, cursor[1])]
149 offset = nmin(nsub(cursor[0], self.last_change[0]),
150 nsub(cursor[1], self.last_change[1]))
151
152 if a_line > 0 and b_line == 0:
153 self.removals = True
154 self.last_change[0] = a_line
155 elif a_line == 0 and b_line > 0:
156 self.additions = True
157 self.last_change[1] = b_line
158
159 if (offset is not None and
160 (offset > CONTEXT_THRESHOLD or
161 (offset > 1 and self.block_closed))):
162 # If the last chunk is too far away, assume a new one starts.
163 self.flush_chunk()
164
165 # Try to figure out block indent from properties exclusively used for
166 # policy definitions.
167 if keyword in ('id', 'schema', 'future', 'items', 'features',
168 'supported_on', 'example_value', 'deprecated'):
169 self.block_indent = line_indent
170
171 # Prefer the comment on the policy name property if we see it fly by.
172 if keyword == 'name':
173 # Attempt to filter out name labels on enum items.
174 if self.block_indent is not None and self.block_indent != line_indent:
175 pass
176 elif a_line > 0 and b_line == 0:
177 self.comment_pos[0] = a_line
178 elif a_line == 0 and b_line > 0:
179 self.comment_pos[1] = b_line
180
181 self.chunk_start = [nmin(self.last_change[0], self.chunk_start[0]),
182 nmin(self.last_change[1], self.chunk_start[1])]
183
184 # Flush the last chunk.
185 if self.chunk_start != [None, None]:
186 self.flush_chunk()
187
188 @property
189 def chunks(self):
190 return self.chunks_list
191
192 def flush_chunk(self):
193 self.comment_pos = [nmax(self.chunk_start[0], self.comment_pos[0]),
194 nmax(self.chunk_start[1], self.comment_pos[1])]
195 self.chunks_list.append(
196 util.ObjectDict(
197 { 'start': self.chunk_start,
198 'end': self.last_change,
199 'comment_pos': self.comment_pos,
200 'additions': self.additions,
201 'removals': self.removals }))
202 self.reset()
203
204 def reset(self):
205 # This is called from __init__.
206 # pylint: disable=W0201
207 self.chunk_start = [None, None]
208 self.last_change = [None, None]
209 self.comment_pos = [None, None]
210 self.block_indent = None
211 self.block_closed = False
212 self.additions = False
213 self.removals = False
214
215
216 def process(addr, message, review, rietveld):
217 """Handles reviews for chrome/app/policy/policy_templates.json.
218
219 This looks at the patch to identify additions/modifications to policy
220 definitions and posts comments with a checklist intended for the author and
221 reviewer to go through in order to catch common mistakes.
222 """
223
224 if POLICY_TEMPLATES_FILE not in review.latest_patchset.files:
225 return
226
227 # Only process the change if the mail is directly to us or we haven't
228 # processed this review yet.
229 client_id = model.app_config.get().client_id
230 if (not addr in util.get_emails(getattr(message, 'to', '')) and
231 client_id in [m.sender for m in review.issue_data.messages]):
232 return
iannucci 2013/08/15 03:48:05 So the idea here is that you could add reviewbot a
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 Correct. Once we've done so a couple times and con
233
234 # Don't process reverts.
235 if 'revert' in review.issue_data.description.lower():
236 return
237
238 # Parse the patch, look at the chunks and generate inline comments.
239 parser = PolicyChangeParser(
iannucci 2013/08/15 03:48:05 why not just make PolicyChangeParser a function re
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 It's got all this state to carry around, which is
240 review.latest_patchset.files[POLICY_TEMPLATES_FILE].patch.lines)
241 parser.run()
242 for chunk in parser.chunks:
243 if chunk.additions and not chunk.removals:
244 message = ADDITION_COMMENT
245 else:
246 message = MODIFICATION_COMMENT
247
248 if chunk.comment_pos[1] is not None:
249 line, side = chunk.comment_pos[1], 'b'
250 elif chunk.comment_pos[0] is not None:
251 line, side = chunk.comment_pos[0], 'a'
252 else:
253 # No suitable position?
254 continue
255
256 rietveld.add_inline_comment(
257 review.issue_id, review.latest_patchset.patchset,
258 review.latest_patchset.files[POLICY_TEMPLATES_FILE].id,
259 line, side, message)
iannucci 2013/08/15 03:48:05 Maybe I'm unfamiliar with the types of changes her
Mattias Nissler (ping if slow) 2013/08/16 12:49:22 I've tried it on GIT history and it usually posted
260
261 # Finally, post all inline comments.
262 if len(parser.chunks) > 0:
263 rietveld.post_comment(review.issue_id, REVIEW_MESSAGE, True)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698