# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# CONFIGURE_TRACKER.PY -- BUILD P4DTI CONFIGURATION FOR TRACKER
#
# Robert Cowham, Vaccaperna Systems Limited, 2003-11-20
#
#
# 1. INTRODUCTION
#
# This module defines a configuration generator for the Tracker
# integration. Configuration generators are documented in detail in
# [GDR 2000-10-16, 8].
#
# The intended readership of this document is project developers.
#
# This document is not confidential.
import os
import catalog
import check_config
import dt_tracker
import logger
import re
import string
import tracker
import time
import translator
import types
error = "Tracker configuration error"
# 2. BUILD THE MAPPING BETWEEN STATES IN TRACKER AND PERFORCE
#
# The make_state_pairs function takes a Tracker connection and the
# "closed state", and returns a list of pairs of state names (Tracker
# state, Perforce state). This list will be used to translate between
# states, and also to generate the possible values for the State field
# in Perforce.
#
# The closed_state argument is the Tracker state which maps to the
# special state 'closed' in Perforce, or None if there is no such state.
# See requirement 45. See the decision decision [RB 2000-11-28b].
#
# The case of state names in these pairs is normalized for usability in
# Perforce: see the design decision [RB 2000-11-28a].
keyword_translator = translator.keyword_translator()
# 3. CONVERT DATE/TIME TO SECONDS
#
# This function converts a date/time in standard format, like
# '2001-02-12 19:19:24' [ISO 8601] into seconds since the epoch.
#
# We use this to convert the start_date configuration parameter. It is
# specified as an date/time for ease of entry, but Tracker represents
# date/times as seconds since the epoch. (Note that we specify -1 for
# the DST flag -- see job000381).
def convert_isodate_to_secs(isodate):
assert isinstance(isodate, types.StringType)
date_re = "^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$"
match = re.match(date_re, isodate)
assert match
return time.mktime(tuple(map(int, match.groups()) + [0,0,-1]))
# 4. MIGRATION FUNCTIONS
def prepare_issue_advanced(config, tr, p4, dict, job):
# Call user hook (see [GDR 2001-11-14, 3]).
config.prepare_issue(dict, job)
def translate_jobspec_advanced(config, dt, p4, job):
return config.translate_jobspec(job)
# Function which does global mapping of different fields from p4 jobs to issues
# User Hook
def translate_p4_to_dt(issue, job):
changes = {}
if job['State'] == 'closed':
if not issue.bug.has_key('Resolution') or issue.bug['Resolution'] == '':
changes['Resolution'] = 'Fixed'
if not issue.bug.has_key('Fixed Date') or issue.bug['Fixed Date'] == '':
changes['Fixed Date'] = time.strftime("%d/%m/%Y %H:%M:%S", time.gmtime())
return changes
# Function which does global mapping of from issues to jobs
# User Hook
def translate_dt_to_p4(issue, job):
changes = {}
if issue['State'] == 'Closed':
if job['State'] <> 'closed':
changes['State'] = 'closed'
return changes
# 5. BUILD P4DTI CONFIGURATION FOR TRACKER
def configuration(config):
# 5.1. Check Tracker-specific configuration parameters
check_config.check_string(config, 'tracker_user')
check_config.check_string(config, 'tracker_password')
check_config.check_string(config, 'tracker_project')
check_config.check_string(config, 'tracker_server')
check_config.check_bool(config, 'use_windows_event_log')
if os.name <> 'nt':
# "Tracker integration only runs on Windows"
raise error, catalog.msg(1300)
# Set up loggers
loggers = []
log_params = {
'priority': config.log_level,
'max_length': config.log_max_message_length,
}
# The log messages should go to (up to) three places:
# 1. to standard output (if running from a command line);
if config.use_stdout_log:
loggers.append(apply(logger.file_logger, (), log_params))
# 2. to the file named by the log_file configuration parameter (if
# not None);
if config.log_file != None:
loggers.append(apply(logger.file_logger,
(open(config.log_file, "a"),),
log_params))
# 3. to the Windows event log (if use_windows_event_log is true).
if config.use_windows_event_log:
loggers.append(apply(logger.win32_event_logger,
(config.rid,),
log_params))
config.logger = logger.multi_logger(loggers)
# 5.2. Open a connection to the Tracker server
trk = tracker.tracker(config,
config.tracker_user,
config.tracker_password,
config.tracker_project,
config.tracker_server)
config.trk = trk
config.jobname_function = lambda bug: 'SCR%s' % bug['Id']
# 5.3. Translators
#
date_translator = dt_tracker.date_translator()
# elapsed_time_translator = dt_tracker.elapsed_time_translator()
# int_translator = dt_tracker.int_translator()
# float_translator = dt_tracker.float_translator()
text_translator = dt_tracker.text_translator()
text_line_translator = dt_tracker.text_line_translator()
# strict user translator doesn't allow unknown users
strict_user_translator = dt_tracker.user_translator(
config.replicator_address, config.p4_user, allow_unknown = 0)
# lax user translator does allow unknown users
lax_user_translator = dt_tracker.user_translator(
config.replicator_address, config.p4_user, allow_unknown = 1)
user_translator = lax_user_translator
# RHGC - Hard coded status transitions
state_pairs = []
state_pairs.append(('Open', 'open'))
state_pairs.append(('Closed', 'closed'))
state_translator = dt_tracker.status_translator(state_pairs)
# 5.6. Make values for the State field in Perforce
#
# Work out the legal values of the State field in Perforce. Note
# that "closed" must be a legal state because "p4 fix -c CHANGE
# JOBNAME" always sets the State to "closed" even if "closed" is not
# a legal value. See job000225.
legal_states = map((lambda x: x[1]), state_pairs)
if 'closed' not in legal_states:
legal_states.append('closed')
state_values = string.join(legal_states, '/')
# 5.7. Fields that always appear in the Perforce jobspec
#
# The 'p4_fields' table maps Tracker field name to a definition of
# the corresponding field in Perforce. The table also has entries
# for field not replicated from Tracker: these appear under dummy
# Tracker field names in parentheses.
#
# Perforce field definitions have nine elements:
#
# 1. Field number;
#
# 2. Field name;
#
# 3. Field type (word, line, select, date, text);
#
# 4. Field length;
#
# 5. Field disposition (always, required, optional, default);
#
# 6. The default value for the field, or None if there isn't one
# (field Preset);
#
# 7. Legal values for the field (if it's a "select" field) or None
# otherwise (field Values);
#
# 8. Help text for the field;
#
# 9. Translator for the field (if the field is replicated from
# Tracker), or None (if the field is not replicated).
#
# The five fields 101 to 105 are predefined because they are
# required by Perforce. The fields Job and Date are special: they
# are required by Perforce but are not replicated from Tracker.
# Note that their help text is given (the other help texts will be
# fetched from Tracker).
#
# We extend this table with fields from the "replicated_fields"
# configuration parameter (section 5.8). Next we use the table to
# buid the Perforce jobspec (section 5.9). Finally, we use the
# table to build the "field_map" configuration parameter which the
# replicator module uses to replicate the field (section 5.10).
p4_fields = {
'(JOB)': ( 101, 'Job', 'word', 32, 'required',
None, None,
"The job name.",
None ),
'(State)': ( 102, 'State', 'select', 32, 'required',
state_pairs[0][1], state_values,
"Issue's fixed status in Tracker",
None ),
'Owner': ( 103, 'Owner', 'word', 32, 'required',
'$user', None,
"Owner of issue in Tracker",
user_translator ),
'(DATE)': ( 104, 'Date', 'date', 20, 'always',
'$now', None,
"The date this job was last modified.",
None ),
'Title': ( 105, 'Title', 'line', 0, 'required',
'$blank', None,
"Title of Issue",
text_line_translator ),
'Description': ( 110, 'Description', 'text', 0, 'optional',
'$blank', None,
"Description of issue.",
text_translator ),
'(RID)': ( 192, 'P4DTI-rid', 'word', 32, 'required',
'None', None,
"P4DTI replicator identifier. Do not edit!",
None ),
'(ISSUE)': ( 193, 'P4DTI-issue-id', 'word', 32, 'required',
'None', None,
"Tracker issue database identifier. Do not "
"edit!",
None ),
'(USER)': ( 194, 'P4DTI-user', 'word', 32, 'always',
'$user', None,
"Last user to edit this job. You can't edit "
"this!",
None ),
}
# Tracker fields that are read and their type.
# Used to read appropriate type in Tracker.py
#
# int - integer
# desc - description (can be greater than 255 - only 1 allowed per record
# string - string (up to 255)
#
tr_fields = {
'Id': ( 'int' ),
'Title': ( 'string' ),
'Owner': ( 'string' ),
'Description': ( 'desc' ),
'State': ( 'string' ),
'Resolution': ( 'string' ),
'Fixed Date': ( 'string' ),
'Submit Date': ( 'string' ),
'rid': ( 'string' ),
}
# For the first phase of this integration (can be changed in future):
# Some Tracker fields should not be changed from Perforce.
read_only_fields = ['Id',
'Title',
'Description',
'Owner']
# 5.9. Make jobspec description
comment = ("# A Perforce Job Specification automatically "
"produced by the\n"
"# Perforce Defect Tracking Integration\n")
jobspec = (comment, p4_fields.values())
# 5.10. Generate configuration parameters
# Set configuration parameters needed by dt_tracker.
config.start_date = convert_isodate_to_secs(config.start_date)
config.state_pairs = state_pairs
config.read_only_fields = read_only_fields
config.tr_fields = tr_fields
# Set configuration parameters needed by the replicator.
config.date_translator = date_translator
config.job_owner_field = 'Owner'
config.job_status_field = 'State'
config.job_date_field = 'Date'
config.jobspec = jobspec
config.prepare_issue_advanced = prepare_issue_advanced
config.translate_jobspec_advanced = translate_jobspec_advanced
config.text_translator = text_translator
config.user_translator = user_translator
config.translate_p4_to_dt = translate_p4_to_dt
config.translate_dt_to_p4 = translate_dt_to_p4
# The field_map parameter is a list of triples (Tracker database
# field name, Perforce field name, translator) required by the
# replicator.
#
# This is generated from the p4_field table by filtering out fields
# that aren't replicated (these have no translator) and selecting
# only the three elements of interest.
config.field_map = \
map(lambda item: (item[0], item[1][1], item[1][8]),
filter(lambda item: item[1][8] != None, p4_fields.items()))
return config
# A. REFERENCES
#
# [GDR 2001-11-14] "Perforce Defect Tracking Integration Advanced
# Administrator's Guide"; Gareth Rees; Ravenbrook Limited; 2001-11-14;
# <http://www.ravenbrook.com/project/p4dti/version/1.5/manual/aag/>.
#
# [GDR 2000-10-16] "Perforce Defect Tracking Integration Integrator's
# Guide"; Richard Brooksby; Ravenbrook Limited; 2000-10-16;
# <http://www.ravenbrook.com/project/p4dti/version/1.5/manual/ig/>.
#
# [GDR 2000-10-16] "Perforce Defect Tracking Integration Integrator's
# Guide"; Gareth Rees; Ravenbrook Limited; 2000-10-16;
# <http://www.ravenbrook.com/project/p4dti/version/1.5/manual/ig/>.
#
# [ISO 8601] "Representation of dates and times"; ISO; 1988-06-15.
#
# [RB 2000-11-28a] "Case of state names" (e-mail message); Richard
# Brooksby; Ravenbrook; 2000-11-28;
# <http://info.ravenbrook.com/mail/2000/11/28/14-24-32/0.txt>.
#
#
# B. DOCUMENT HISTORY
#
# 2002-04-05 NB job000501: handle creation of new jobs when
# LASTMODIFIEDDATE or SUBMITDATE are replicated
#
#
# C. COPYRIGHT AND LICENCE
#
# This file is copyright (c) 2003 Vaccaperna Systems Ltd. All rights
# reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
#
# $Id: //info.ravenbrook.com/project/p4dti/version/1.5/code/replicator/configure_teamtrack.py#3 $