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

Unified Diff: tracker_spreadsheet_sync

Issue 6287010: crosutils:Remove dead scripts (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/crosutils.git@master
Patch Set: Created 9 years, 11 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
« no previous file with comments | « save_pinned_deps ('k') | upgrade.d/1_init » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tracker_spreadsheet_sync
diff --git a/tracker_spreadsheet_sync b/tracker_spreadsheet_sync
deleted file mode 100755
index dd04d27b5f2a530e37b426ba092d68331c9585aa..0000000000000000000000000000000000000000
--- a/tracker_spreadsheet_sync
+++ /dev/null
@@ -1,622 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# For Spreadsheets:
-try:
- from xml.etree import ElementTree
-except ImportError:
- from elementtree import ElementTree
-import gdata.spreadsheet.service
-import gdata.service
-import atom.service
-import gdata.spreadsheet
-import atom
-
-# For Issue Tracker:
-import gdata.projecthosting.client
-import gdata.projecthosting.data
-import gdata.gauth
-import gdata.client
-import gdata.data
-import atom.http_core
-import atom.core
-
-# For this script:
-import getpass
-from optparse import OptionParser
-import pickle
-from sets import Set
-
-# Settings
-credentials_store = 'creds.dat'
-
-class Merger(object):
- def __init__(self, ss_key, ss_ws_key, tracker_message, tracker_project,
- debug, pretend):
- self.ss_key = ss_key
- self.ss_ws_key = ss_ws_key
- self.tracker_message = tracker_message
- self.tracker_project = tracker_project
- self.debug_enabled = debug
- self.pretend = pretend
- self.user_agent = 'adlr-tracker-spreadsheet-merger'
- self.it_keys = ['id', 'owner', 'status', 'title']
-
- def debug(self, message):
- """Prints message if debug mode is set."""
- if self.debug_enabled:
- print message
-
- def print_feed(self, feed):
- 'Handy for debugging'
- for i, entry in enumerate(feed.entry):
- print 'id:', entry.id
- if isinstance(feed, gdata.spreadsheet.SpreadsheetsCellsFeed):
- print '%s %s\n' % (entry.title.text, entry.content.text)
- elif isinstance(feed, gdata.spreadsheet.SpreadsheetsListFeed):
- print '%s %s %s' % (i, entry.title.text, entry.content.text)
- # Print this row's value for each column (the custom dictionary is
- # built using the gsx: elements in the entry.)
- print 'Contents:'
- for key in entry.custom:
- print ' %s: %s' % (key, entry.custom[key].text)
- print '\n',
- else:
- print '%s %s\n' % (i, entry.title.text)
-
-
- def tracker_login(self):
- """Logs user into Tracker, using cached credentials if possible.
- Saves credentials after login."""
- self.it_client = gdata.projecthosting.client.ProjectHostingClient()
- self.it_client.source = self.user_agent
-
- self.load_creds()
-
- if self.tracker_token and self.tracker_user:
- print 'Using existing credential for tracker login'
- self.it_client.auth_token = self.tracker_token
- else:
- self.tracker_user = raw_input('Issue Tracker Login:')
- password = getpass.getpass('Password:')
- self.it_client.ClientLogin(self.tracker_user, password,
- source=self.user_agent, service='code',
- account_type='GOOGLE')
- self.tracker_token = self.it_client.auth_token
- self.store_creds()
-
- def spreadsheet_login(self):
- """Logs user into Google Spreadsheets, using cached credentials if possible.
- Saves credentials after login."""
- self.gd_client = gdata.spreadsheet.service.SpreadsheetsService()
- self.gd_client.source = self.user_agent
-
- self.load_creds()
- if self.docs_token:
- print 'Using existing credential for docs login'
- self.gd_client.SetClientLoginToken(self.docs_token)
- else:
- self.gd_client.email = raw_input('Google Docs Login:')
- self.gd_client.password = getpass.getpass('Password:')
- self.gd_client.ProgrammaticLogin()
- self.docs_token = self.gd_client.GetClientLoginToken()
- self.store_creds()
-
- def fetch_spreadsheet_issues(self):
- """Fetches all issues from the user-specified spreadsheet. Returns
- them as an array or dictionaries."""
- feed = self.gd_client.GetListFeed(self.ss_key, self.ss_ws_key)
- issues = []
- for entry in feed.entry:
- issue = {}
- for key in entry.custom:
- issue[key] = entry.custom[key].text
- issue['__raw_entry'] = entry
- issues.append(issue)
- return issues
-
- def ids_for_spreadsheet_issues(self, ss_issues):
- """Returns a Set of strings, each string an id from ss_issues"""
- ret = Set()
- for ss_issue in ss_issues:
- ret.add(ss_issue['id'])
- return ret
-
- def tracker_issues_for_query_feed(self, feed):
- """Converts a feed object from a query to a list of tracker issue
- dictionaries."""
- issues = []
- for issue in feed.entry:
- issue_dict = {}
- issue_dict['labels'] = [label.text for label in issue.label]
- issue_dict['id'] = issue.id.text.split('/')[-1]
- issue_dict['title'] = issue.title.text
- issue_dict['status'] = issue.status.text
- if issue.owner:
- issue_dict['owner'] = issue.owner.username.text
- issues.append(issue_dict)
- return issues
-
- def fetch_tracker_issues(self, ss_issues):
- """Fetches all relevant issues from traacker and returns them as an array
- of dictionaries. Relevance is:
- - has an ID that's in ss_issues, OR
- - (is Area=Installer AND status is open).
- Open status is one of: Unconfirmed, Untriaged, Available, Assigned,
- Started, Upstream"""
- issues = []
- got_results = True
- index = 1
- while got_results:
- query = gdata.projecthosting.client.Query(label='Area-Installer',
- max_results=50,
- start_index=index)
- feed = self.it_client.get_issues('chromium-os', query=query)
- if not feed.entry:
- got_results = False
- index = index + len(feed.entry)
- issues.extend(self.tracker_issues_for_query_feed(feed))
- # Now, remove issues that are open or in ss_issues.
- ss_ids = self.ids_for_spreadsheet_issues(ss_issues)
- open_statuses = ['Unconfirmed', 'Untriaged', 'Available', 'Assigned',
- 'Started', 'Upstream']
- new_issues = []
- for issue in issues:
- if issue['status'] in open_statuses or issue['id'] in ss_ids:
- new_issues.append(issue)
- # Remove id from ss_ids, if it's there
- ss_ids.discard(issue['id'])
- issues = new_issues
-
- # Now, for each ss_id that didn't turn up in the query, explicitly add it
- for id_ in ss_ids:
- query = gdata.projecthosting.client.Query(issue_id=id_,
- max_results=50,
- start_index=index)
- feed = self.it_client.get_issues('chromium-os', query=query)
- if not feed.entry:
- print 'No result for id', id_
- continue
- issues.extend(self.tracker_issues_for_query_feed(feed))
-
- return issues
-
- def store_creds(self):
- """Stores login credentials to disk."""
- obj = {}
- if self.docs_token:
- obj['docs_token'] = self.docs_token
- if self.tracker_token:
- obj['tracker_token'] = self.tracker_token
- if self.tracker_user:
- obj['tracker_user'] = self.tracker_user
- try:
- f = open(credentials_store, 'w')
- pickle.dump(obj, f)
- f.close()
- except IOError:
- print 'Unable to store credentials'
-
- def load_creds(self):
- """Loads login credentials from disk."""
- self.docs_token = None
- self.tracker_token = None
- self.tracker_user = None
- try:
- f = open(credentials_store, 'r')
- obj = pickle.load(f)
- f.close()
- if obj.has_key('docs_token'):
- self.docs_token = obj['docs_token']
- if obj.has_key('tracker_token'):
- self.tracker_token = obj['tracker_token']
- if obj.has_key('tracker_user'):
- self.tracker_user = obj['tracker_user']
- except IOError:
- print 'Unable to load credentials'
-
- def browse(self):
- """Browses Spreadsheets to help the user find the spreadsheet and
- worksheet keys"""
- print 'Browsing spreadsheets...'
-
- if self.ss_key and self.ss_ws_key:
- print 'You already passed in --ss_key and --ss_ws_key. No need to browse.'
- return
-
- print 'Logging in...'
- self.spreadsheet_login()
-
- if not self.ss_key:
- print 'Fetching spreadsheets...'
- feed = self.gd_client.GetSpreadsheetsFeed()
- print ''
- print 'Spreadsheet key - Title'
- for entry in feed.entry:
- key = entry.id.text.split('/')[-1]
- title = entry.title.text
- print '"%s" - "%s"' % (key, title)
- print ''
- print 'Done. Rerun with --ss_key=KEY to browse a list of worksheet keys.'
- else:
- print 'Fetching worksheets for spreadsheet', self.ss_key
- feed = self.gd_client.GetWorksheetsFeed(self.ss_key)
- for entry in feed.entry:
- key = entry.id.text.split('/')[-1]
- title = entry.title.text
- print ''
- print 'Worksheet key - Title'
- print '"%s" - "%s"' % (key, title)
- print ''
- print 'Done. You now have keys for --ss_key and --ss_ws_key.'
-
- def tracker_issue_for_id(self, issues, id_):
- """Returns the element of issues which has id_ for the key 'id'"""
- for issue in issues:
- if issue['id'] == id_:
- return issue
- return None
-
- def spreadsheet_issue_to_tracker_dict(self, ss_issue):
- """Converts a spreadsheet issue to the dict format that is used to
- represent a tracker issue."""
- ret = {}
- ret['project'] = self.tracker_project
- ret['title'] = ss_issue['title']
- ret['summary'] = self.tracker_message
- ret['owner'] = ss_issue['owner']
- if ss_issue.get('status') is not None:
- ret['status'] = ss_issue['status']
- ret['labels'] = []
- for (key, value) in ss_issue.items():
- if key.endswith('-') and (value is not None):
- ret['labels'].append(key.title() + value)
- return ret
-
- def label_from_prefix(self, prefix, corpus):
- """Given a corpus (array of lable strings), return the first label
- that begins with the specified prefix."""
- for label in corpus:
- if label.startswith(prefix):
- return label
- return None
-
- def update_spreadsheet_issue_to_tracker_dict(self, ss_issue, t_issue):
- """Updates a given tracker issue with data from the spreadsheet issue."""
- ret = {}
- ret['title'] = ss_issue['title']
- ret['id'] = ss_issue['id']
- ret['summary'] = self.tracker_message
- if ss_issue['status'] != t_issue['status']:
- ret['status'] = ss_issue['status']
-
- if ss_issue.get('owner'):
- if (not t_issue.has_key('owner')) or \
- (ss_issue['owner'] != t_issue['owner']):
- ret['owner'] = ss_issue['owner']
- # labels
- ret['labels'] = []
- for (key, value) in ss_issue.items():
- caps_key = key.title()
- if not caps_key.endswith('-'):
- continue
- ss_label = None
- if value:
- ss_label = caps_key + value.title()
- t_label = self.label_from_prefix(caps_key, t_issue['labels'])
-
- if t_label is None and ss_label is None:
- # Nothing
- continue
-
- if (t_label is not None) and \
- ((ss_label is None) or (ss_label != t_label)):
- ret['labels'].append('-' + t_label)
-
- if (ss_label is not None) and \
- ((t_label is None) or (t_label != ss_label)):
- ret['labels'].append(ss_label)
- return ret
-
- def tracker_issue_has_changed(self, t_issue, ss_issue):
- """Returns True iff ss_issue indicates changes in t_issue that need to be
- committed up to the Issue Tracker."""
- if t_issue is None:
- return True
- potential_commit = \
- self.update_spreadsheet_issue_to_tracker_dict(ss_issue, t_issue)
-
- if potential_commit.has_key('status') or \
- potential_commit.has_key('owner') or \
- (len(potential_commit['labels']) > 0):
- return True
- if potential_commit['title'] != t_issue['title']:
- return True
- return False
-
- def spreadsheet_to_tracker_commits(self, ss_issues, t_issues):
- """Given the current state of all spreadsheet issues and tracker issues,
- returns a list of all commits that need to go to tracker to get it in
- line with the spreadsheet."""
- ret = []
- for ss_issue in ss_issues:
- t_issue = self.tracker_issue_for_id(t_issues, ss_issue['id'])
- commit = {}
- # TODO see if an update is needed at all
- if t_issue is None:
- commit['type'] = 'append'
- commit['dict'] = self.spreadsheet_issue_to_tracker_dict(ss_issue)
- commit['__ss_issue'] = ss_issue
- else:
- if not self.tracker_issue_has_changed(t_issue, ss_issue):
- continue
- commit['type'] = 'update'
- commit['dict'] = \
- self.update_spreadsheet_issue_to_tracker_dict(ss_issue, t_issue)
- ret.append(commit)
- return ret
-
- def fetch_issues(self):
- """Logs into Docs/Tracker, and fetches spreadsheet and tracker issues"""
- print 'Logging into Docs...'
- self.spreadsheet_login()
- print 'Logging into Tracker...'
- self.tracker_login()
-
- print 'Fetching spreadsheet issues...'
- ss_issues = self.fetch_spreadsheet_issues()
- self.debug('Spreadsheet issues: %s' % ss_issues)
- print 'Fetching tracker issues...'
- t_issues = self.fetch_tracker_issues(ss_issues)
- self.debug('Tracker issues: %s' % t_issues)
- return (t_issues, ss_issues)
-
- def spreadsheet_to_tracker(self):
- """High-level function to manage migrating data from the spreadsheet
- to Tracker."""
- (t_issues, ss_issues) = self.fetch_issues()
- print 'Calculating deltas...'
- commits = self.spreadsheet_to_tracker_commits(ss_issues, t_issues)
- self.debug('got commits: %s' % commits)
- if not commits:
- print 'No deltas. Done.'
- return
-
- for commit in commits:
- dic = commit['dict']
- labels = dic.get('labels')
- owner = dic.get('owner')
- status = dic.get('status')
-
- if commit['type'] == 'append':
- print 'Creating new tracker issue...'
- if self.pretend:
- print '(Skipping because --pretend is set)'
- continue
- created = self.it_client.add_issue(self.tracker_project,
- dic['title'],
- self.tracker_message,
- self.tracker_user,
- labels=labels,
- owner=owner,
- status=status)
- issue_id = created.id.text.split('/')[-1]
- print 'Created issue with id:', issue_id
- print 'Write id back to spreadsheet row...'
- raw_entry = commit['__ss_issue']['__raw_entry']
- ss_issue = commit['__ss_issue']
- del ss_issue['__raw_entry']
- ss_issue.update({'id': issue_id})
- self.gd_client.UpdateRow(raw_entry, ss_issue)
- print 'Done.'
- else:
- print 'Updating issue with id:', dic['id']
- if self.pretend:
- print '(Skipping because --pretend is set)'
- continue
- self.it_client.update_issue(self.tracker_project,
- dic['id'],
- self.tracker_user,
- comment=self.tracker_message,
- status=status,
- owner=owner,
- labels=labels)
- print 'Done.'
-
- def spreadsheet_issue_for_id(self, issues, id_):
- """Given the array of spreadsheet issues, return the first one that
- has id_ for the key 'id'."""
- for issue in issues:
- if issue['id'] == id_:
- return issue
- return None
-
- def value_for_key_in_labels(self, label_array, prefix):
- """Given an array of labels and a prefix, return the non-prefix part
- of the first label that has that prefix. E.g. if label_array is
- ["Mstone-R7", "Area-Installer"] and prefix is "Area-", returns
- "Installer"."""
- for label in label_array:
- if label.startswith(prefix):
- return label[len(prefix):]
- return None
-
- def tracker_issue_to_spreadsheet_issue(self, t_issue, ss_keys):
- """Converts a tracker issue to the format used by spreadsheet, given
- the row headings ss_keys."""
- new_row = {}
- for key in ss_keys:
- if key.endswith('-'):
- # label
- new_row[key] = self.value_for_key_in_labels(t_issue['labels'],
- key.title())
- # Special cases
- if key in self.it_keys and key in t_issue:
- new_row[key] = t_issue[key]
- return new_row
-
- def spreadsheet_row_needs_update(self, ss_issue, t_issue):
- """Returns True iff the spreadsheet issue passed in needs to be updated
- to match data in the tracker issue."""
- new_ss_issue = self.tracker_issue_to_spreadsheet_issue(t_issue,
- ss_issue.keys())
- for key in new_ss_issue.keys():
- if not ss_issue.has_key(key):
- continue
- if new_ss_issue[key] != ss_issue[key]:
- return True
- return False
-
- def tracker_to_spreadsheet_commits(self, t_issues, ss_issues):
- """Given the current set of spreadsheet and tracker issues, computes
- commits needed to go to Spreadsheets to get the spreadsheet in line
- with what's in Tracker."""
- ret = []
- keys = ss_issues[0].keys()
- for t_issue in t_issues:
- commit = {}
- ss_issue = self.spreadsheet_issue_for_id(ss_issues, t_issue['id'])
- if ss_issue is None:
- # New issue
- commit['new_row'] = self.tracker_issue_to_spreadsheet_issue(t_issue,
- keys)
- commit['type'] = 'append'
- elif self.spreadsheet_row_needs_update(ss_issue, t_issue):
- commit['__raw_entry'] = ss_issue['__raw_entry']
- del ss_issue['__raw_entry']
- ss_issue.update(self.tracker_issue_to_spreadsheet_issue(t_issue, keys))
- commit['dict'] = ss_issue
- commit['type'] = 'update'
- else:
- continue
- ret.append(commit)
- return ret
-
- def tracker_to_spreadsheet(self):
- """High-level function to migrate data from Tracker to the spreadsheet."""
- (t_issues, ss_issues) = self.fetch_issues()
- if len(ss_issues) == 0:
- raise Exception('Error: must have at least one non-header row in '\
- 'spreadsheet')
- return
- ss_keys = ss_issues[0].keys()
-
- print 'Calculating deltas...'
- ss_commits = self.tracker_to_spreadsheet_commits(t_issues, ss_issues)
- self.debug('commits: %s' % ss_commits)
- if not ss_commits:
- print 'Nothing to commit.'
- return
- print 'Committing...'
- for commit in ss_commits:
- self.debug('Operating on commit: %s' % commit)
- if commit['type'] == 'append':
- print 'Appending new row...'
- if not self.pretend:
- self.gd_client.InsertRow(commit['new_row'],
- self.ss_key, self.ss_ws_key)
- else:
- print '(Skipped because --pretend set)'
- if commit['type'] == 'update':
- print 'Updating row...'
- if not self.pretend:
- self.gd_client.UpdateRow(commit['__raw_entry'], commit['dict'])
- else:
- print '(Skipped because --pretend set)'
- print 'Done.'
-
-def main():
- class PureEpilogOptionParser(OptionParser):
- def format_epilog(self, formatter):
- return self.epilog
-
- parser = PureEpilogOptionParser()
- parser.add_option('-a', '--action', dest='action', metavar='ACTION',
- help='Action to perform')
- parser.add_option('-d', '--debug', action='store_true', dest='debug',
- default=False, help='Print debug output.')
- parser.add_option('-m', '--message', dest='message', metavar='TEXT',
- help='Log message when updating Tracker issues')
- parser.add_option('-p', '--pretend', action='store_true', dest='pretend',
- default=False, help="Don't commit anything.")
- parser.add_option('--ss_key', dest='ss_key', metavar='KEY',
- help='Spreadsheets key (find with browse action)')
- parser.add_option('--ss_ws_key', dest='ss_ws_key', metavar='KEY',
- help='Spreadsheets worksheet key (find with browse action)')
- parser.add_option('--tracker_project', dest='tracker_project',
- metavar='PROJECT',
- help='Tracker project (default: chromium-os)',
- default='chromium-os')
- parser.epilog = """Actions:
- browse -- browse spreadsheets to find spreadsheet and worksheet keys.
- ss_to_t -- for each entry in spreadsheet, apply its values to tracker.
- If no ID is in the spreadsheet row, a new tracker item is created
- and the spreadsheet is updated.
- t_to_ss -- for each tracker entry, apply it or add it to the spreadsheet.
-
-
-This script can be used to migrate Issue Tracker issues between Issue Tracker
-and Google Spreadsheets. The spreadsheet should have certain columns in any
-order: Id, Owner, Title, Status. The spreadsheet may have any label of the
-form 'Key-'. For those labels that end in '-', this script assumes the cell
-value and the header form a label that should be applied to the issue. E.g.
-if the spredsheet has a column named 'Mstone-' and a cell under it called
-'R8' that corresponds to the label 'Mstone-R8' in Issue Tracker.
-
-To migrate data, you must choose on each invocation of this script if you
-wish to migrate data from Issue Tracker to a spreadsheet of vice-versa.
-
-When migrating from Tracker, all found issues based on the query
-(which is currently hard-coded to "label=Area-Installer") will be inserted
-into the spreadsheet (overwritng existing cells if a row with matching ID
-is found). Custom columns in the spreadsheet won't be overwritten, so if
-the spreadsheet contains extra columns about issues (e.g. time estimates)
-they will be preserved.
-
-When migrating from spreadsheet to Tracker, each row in the spreadsheet
-is compared to existing tracker issues that match the query
-(which is currently hard-coded to "label=Area-Installer"). If the
-spreadsheet row has no Id, a new Issue Tracker issue is created and the new
-Id is written back to the spreadsheet. If an existing tracker issue exists,
-it's updated with the data from the spreadsheet if anything has changed.
-
-Suggested usage:
-- Create a spreadsheet with columns Id, Owner, Title, Status, and any label
-prefixes as desired.
-- Run this script with '-b' to browse your spreadsheet and get the
-spreadsheet key.
-- Run this script again with '-b' and the spreadsheet key to get the
-worksheet key.
-- Run this script with "-a t_to_ss" or "-a ss_to_t" to migrate data in either
-direction.
-
-Known issues:
-- query is currently hardcoded to label=Area-Installer. That should be
-a command-line flag.
-- When creating a new issue on tracker, the owner field isn't set. I (adlr)
-am not sure why. Workaround: If you rerun this script, tho, it will detect
-a delta and update the tracker issue with the owner, which seems to succeed.
-"""
-
- (options, args) = parser.parse_args()
-
- merger = Merger(options.ss_key, options.ss_ws_key,
- options.message, options.tracker_project,
- options.debug, options.pretend)
- if options.action == 'browse':
- merger.browse()
- elif options.action == 'ss_to_t':
- if not options.message:
- print 'Error: when updating tracker, -m MESSAGE required.'
- return
- merger.spreadsheet_to_tracker()
- elif options.action == 't_to_ss':
- merger.tracker_to_spreadsheet()
- else:
- raise Exception('Unknown action requested.')
-
-if __name__ == '__main__':
- main()
« no previous file with comments | « save_pinned_deps ('k') | upgrade.d/1_init » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698