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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: reviewbot/handlers/policy_checklist.py
===================================================================
--- reviewbot/handlers/policy_checklist.py (revision 0)
+++ reviewbot/handlers/policy_checklist.py (revision 0)
@@ -0,0 +1,263 @@
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import re
+
+import model.app_config
+import util
+
+
+POLICY_TEMPLATES_FILE = 'chrome/app/policy/policy_templates.json'
+ID_LINE_COMMENT = 'For your editing convenience: highest ID currently used:'
+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
+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.
+MAX_LINE = 1000000
+
+REVIEW_MESSAGE = '''
+Thanks for improving Chromium enterprise features! In general,
+enterprise policies should work across all platforms and need to remain
+backward compatible across several Chrome versions. To help avoid common
+pitfalls, policy review bot has analyzed your change and added comments
+with checklists of things to watch out for.
+
+Please direct comments and complaints about this service to
+chrome-enterprise@google.com and/or mnissler@chromium.org.
+'''
+
+ADDITION_COMMENT = '''
+When adding a new policy setting, please consider the following:
+
+- Ideally, your new policy should be expressing the intention of an
+ administrator and/or cover a well-scoped user-visible behavior of the
+ browser as opposed to an implementation-specific parameter. Consider
+ that your implementation may change in the future, and you might have
+ 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/
+- Make sure your policy definition has proper supported_on declarations
+ specifying the platform and versions this policy is supported on.
+- Make sure feature flags are correct. In particular:
+ - dynamic_refresh - whether your feature can adjust to changes in
+ policy at run time. You typically use PrefChangeRegistrar to do so.
+ - per_profile - whether your policy is browser-global or can be set
+ independently for each Chrome Profile. This is usually true if you
+ read the policy value from the Profile's PrefService and false if
+ you read it from the local_state PrefService.
+ - can_be_recommended - whether your feature supports admin-supplied
+ default values that the user can override.
+- Make sure you put a helpful policy description:
+ - Describe the effect of setting the policy in a way that makes sense
+ to somebody who is not intimately familiar with your feature, such
+ as administrators and other Chromium developers.
+ - Do mention behavior for the cases where the policy gets ignored
+ (i.e. not configured, booleans only one value is effective). It's
+ nice for completeness and admins have been asking specifically for
+ this piece of information in the past.
+- Write a browser_test for you new policy. The ideal test would fire up
+ the browser with the policy set and check whether the policy affects
+ user-visible behavior in the intended way. See
+ chrome/browser/policy/policy_browsertest.cc for examples.
+- If you're adding a device policy for Chrome OS, be sure to update
+ chrome/browser/chromeos/policy/device_policy_decoder_chromeos.{h,cc}
+ 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.
+'''
+
+MODIFICATION_COMMENT = '''
+When modifying an existing policy setting, please consider the following:
+
+- Make sure the policy meta data is up-to-date, in particular
+ supported_on, and the feature flags.
+- In general, don't change policy semantics in a way that is
+ incompatible (as determined by user/admin-visible behavior) with
+ previous semantics. In particular, consider that existing policy
+ deployments may affect both old and new browser versions, and both
+ should behave according to the admin's intentions.
+- It is OK to expand semantics of policy values as long as the previous
+ policy description is compatible with the new behavior.
+- It is OK to update feature implementations and the policy description
+ when Chrome changes as long as the intended effect of the policy
+ remains intact.
+- The process for removing policies is to deprecate them first, wait a
+ few releases (if possible) and then remove them. Make sure you put the
+ deprecated flag if you deprecate a policy.
+'''
+
+
+def nmin(*args):
+ """Calculates the minimum of |args|, ignoring None entries."""
+ try:
+ return min(v for v in args if v is not None)
+ 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'
+ return None
+
+
+def nmax(*args):
+ """Calculates the maximum of |args|, ignoring None entries."""
+ try:
+ return max(v for v in args if v is not None)
+ except ValueError:
+ return None
+
+
+def nsub(a, b):
+ """Calculates a - b, returning None if either a or b is None"""
+ return None if (a is None or b is None) else a - b
+
+
+def indent(line):
+ """Returns the indent level (number of leading spaces) for |line|."""
+ nspaces = len(line) - len(line.lstrip(' '))
+ 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
+
+
+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
+ def __init__(self, lines):
+ self.lines = lines
+ self.chunks_list = []
+ self.reset()
+
+ def run(self):
+ self.chunks_list = []
+ self.last_change = [None, None]
+ cursor = [None, None]
+ self.reset()
+ for (a_line, b_line, line) in self.lines:
+ # Skip comment lines.
+ if line.startswith('#'):
+ continue
+
+ # See whether the current line has a JSON property.
+ keyword = None
+ match = PROPERTY_NAME_RE.search(line)
+ if match:
+ keyword = match.group(1).lower()
+
+
+ # Check whether the current block closes.
+ line_indent = indent(line)
+ if (self.block_indent is not None and
+ line_indent is not None and
+ line_indent < self.block_indent):
+ self.block_closed = True
+
+ if (keyword == 'name' and self.block_closed):
+ # If we see the 'name' property, that likely indicates the start of a
+ # new policy. Start a new chunk.
+ self.flush_chunk()
+
+ # Update various cursors.
+ cursor = [nmax(a_line, cursor[0]), nmax(b_line, cursor[1])]
+ offset = nmin(nsub(cursor[0], self.last_change[0]),
+ nsub(cursor[1], self.last_change[1]))
+
+ if a_line > 0 and b_line == 0:
+ self.removals = True
+ self.last_change[0] = a_line
+ elif a_line == 0 and b_line > 0:
+ self.additions = True
+ self.last_change[1] = b_line
+
+ if (offset is not None and
+ (offset > CONTEXT_THRESHOLD or
+ (offset > 1 and self.block_closed))):
+ # If the last chunk is too far away, assume a new one starts.
+ self.flush_chunk()
+
+ # Try to figure out block indent from properties exclusively used for
+ # policy definitions.
+ if keyword in ('id', 'schema', 'future', 'items', 'features',
+ 'supported_on', 'example_value', 'deprecated'):
+ self.block_indent = line_indent
+
+ # Prefer the comment on the policy name property if we see it fly by.
+ if keyword == 'name':
+ # Attempt to filter out name labels on enum items.
+ if self.block_indent is not None and self.block_indent != line_indent:
+ pass
+ elif a_line > 0 and b_line == 0:
+ self.comment_pos[0] = a_line
+ elif a_line == 0 and b_line > 0:
+ self.comment_pos[1] = b_line
+
+ self.chunk_start = [nmin(self.last_change[0], self.chunk_start[0]),
+ nmin(self.last_change[1], self.chunk_start[1])]
+
+ # Flush the last chunk.
+ if self.chunk_start != [None, None]:
+ self.flush_chunk()
+
+ @property
+ def chunks(self):
+ return self.chunks_list
+
+ def flush_chunk(self):
+ self.comment_pos = [nmax(self.chunk_start[0], self.comment_pos[0]),
+ nmax(self.chunk_start[1], self.comment_pos[1])]
+ self.chunks_list.append(
+ util.ObjectDict(
+ { 'start': self.chunk_start,
+ 'end': self.last_change,
+ 'comment_pos': self.comment_pos,
+ 'additions': self.additions,
+ 'removals': self.removals }))
+ self.reset()
+
+ def reset(self):
+ # This is called from __init__.
+ # pylint: disable=W0201
+ self.chunk_start = [None, None]
+ self.last_change = [None, None]
+ self.comment_pos = [None, None]
+ self.block_indent = None
+ self.block_closed = False
+ self.additions = False
+ self.removals = False
+
+
+def process(addr, message, review, rietveld):
+ """Handles reviews for chrome/app/policy/policy_templates.json.
+
+ This looks at the patch to identify additions/modifications to policy
+ definitions and posts comments with a checklist intended for the author and
+ reviewer to go through in order to catch common mistakes.
+ """
+
+ if POLICY_TEMPLATES_FILE not in review.latest_patchset.files:
+ return
+
+ # Only process the change if the mail is directly to us or we haven't
+ # processed this review yet.
+ client_id = model.app_config.get().client_id
+ if (not addr in util.get_emails(getattr(message, 'to', '')) and
+ client_id in [m.sender for m in review.issue_data.messages]):
+ 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
+
+ # Don't process reverts.
+ if 'revert' in review.issue_data.description.lower():
+ return
+
+ # Parse the patch, look at the chunks and generate inline comments.
+ 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
+ review.latest_patchset.files[POLICY_TEMPLATES_FILE].patch.lines)
+ parser.run()
+ for chunk in parser.chunks:
+ if chunk.additions and not chunk.removals:
+ message = ADDITION_COMMENT
+ else:
+ message = MODIFICATION_COMMENT
+
+ if chunk.comment_pos[1] is not None:
+ line, side = chunk.comment_pos[1], 'b'
+ elif chunk.comment_pos[0] is not None:
+ line, side = chunk.comment_pos[0], 'a'
+ else:
+ # No suitable position?
+ continue
+
+ rietveld.add_inline_comment(
+ review.issue_id, review.latest_patchset.patchset,
+ review.latest_patchset.files[POLICY_TEMPLATES_FILE].id,
+ 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
+
+ # Finally, post all inline comments.
+ if len(parser.chunks) > 0:
+ rietveld.post_comment(review.issue_id, REVIEW_MESSAGE, True)
Property changes on: reviewbot/handlers/policy_checklist.py
___________________________________________________________________
Added: svn:eol-style
+ LF

Powered by Google App Engine
This is Rietveld 408576698