def pivotalSource(details): """Produce a CoreJet XML file with stories for epics from Pivotal. The parameter should be a comma-separated string with the following parameters: <epic>,<epic>,... - optional cfg section names to retrieve options per epic token=<token> - default pivotal token to use in authentication project=<project> - default pivotal project id to retrieve stories from filter=<filter> - default pivotal filter string to retrieve stories title=<title> - optional title for the requirements catalog (defaults to the first found pivotal project title) """ sections = [] defaults = {} for option in details.split(","): try: key, value = option.split("=", 1) except ValueError: # values without keys are interpreted as cfg-sections value = option.strip() if value: sections.append(value) continue defaults[key.strip().lower()] = value.strip() defaults = config.read("defaults", defaults) if not sections and "epics" in defaults: sections = [name.strip() for name in defaults["epics"].split(",")] if not sections: sections = ["defaults"] epics = [] for section in sections: options = config.read(section, defaults) assert options.get("token", False),\ u"Pivotal token is a mandatory option." assert options.get("project", False),\ u"Pivotal project id is a mandatory option." assert options.get("filter", False),\ u"Pivotal filter string is a mandatory option." # append filter from command line (when found) if defaults.get("filter", "") not in options["filter"]: options["filter"] += " %s" % defaults["filter"] # set includedone:true if it's not explicitly set otherwise if not "includedone:" in options["filter"]: options["filter"] += " includedone:true" try: pv = pivotal.Pivotal(options["token"], use_https=True) except TypeError: # Support HTTPS on pivotal_py == 0.1.3 pivotal.BASE_URL = "https://www.pivotaltracker.com/services/v3/" pv = pivotal.Pivotal(options["token"]) project = pv.projects(options["project"]) project_etree = project.get_etree() project_title =\ options.get("title", project_etree.findtext("name")) if not type(project_title) == unicode: # ensure unicode project_title = unicode(project_title, "utf-8", "ignore") if not "title" in defaults: defaults["title"] = project_title epic_title = options.get("title", project_title) if not type(epic_title) == unicode: # ensure unicode epic_title = unicode(epic_title, "utf-8", "ignore") epic = Epic(name=section != "defaults" and section or str(sections.index(section) + 1), title=epic_title) stories = project.stories(filter=options["filter"]) stories_etree = stories.get_etree() for node in stories_etree: story = Story(node.findtext("id"), node.findtext("name")) story.status = node.findtext("current_state") if story.status in ["accepted", "rejected"]: story.resolution = story.status story.points = max(1, int(node.findtext("estimate", 0) or 0)) appendScenariosFromPivotalStory(story, node, options) if story.scenarios: epic.stories.append(story) epics.append(epic) catalogue = RequirementsCatalogue(project=defaults["title"], extractTime=datetime.now()) for epic in epics: catalogue.epics.append(epic) return catalogue
def jiraSource(details): """Produce a CoreJet XML file from JIRA. The parameter should be a comma-separated string with the following parameters: username=<username> - username to use to connect password=<password> - password to use to connect url=<url> - url of JIRA instance project=<name> - project name filter=<id> - id of filter that returns stories pointsField=<id> - id of field containing story points epicField=<id> - id of field indicating epic for a story acceptanceCriteriaField=<id> - id of field containing acceptance criteria """ parameters = extractParameters(details) username = parameters["username"] password = parameters["password"] url = parameters["url"] projectName = parameters["project"] filterId = parameters["filter"] pointsFieldId = parameters["pointsfield"] epicFieldId = parameters["epicfield"] acFieldId = parameters["acceptancecriteriafield"] catalogue = RequirementsCatalogue(project=projectName, extractTime=datetime.now()) # Open web service connection wsdl = url + "/rpc/soap/jirasoapservice-v2?wsdl" client = Client(wsdl) # Log into JIRA print "Fetching repository from JIRA instance at", url securityToken = client.service.login(username, password) epicCache = {} statuses = {} resolutions = {} try: # Look up statuses and resolutions for status in client.service.getStatuses(securityToken): statuses[status.id] = status.name for resolution in client.service.getResolutions(securityToken): resolutions[resolution.id] = resolution.name # Fetch all issues issues = client.service.getIssuesFromFilter(securityToken, filterId) for issue in issues: story = Story(issue.key, issue.summary) try: story.points = int(singleValueCustomFieldForIssue(issue, pointsFieldId)) except (ValueError, TypeError): pass story.status = statuses.get(issue.status, None) story.resolution = resolutions.get(issue.resolution, None) epicName = singleValueCustomFieldForIssue(issue, epicFieldId) # See if this epic is a reference to an issue epic = epicCache.get(epicName, None) if epic is None: epicTitle = epicName try: epicIssue = client.service.getIssue(securityToken, epicName) epicTitle = epicIssue.summary except WebFault: # Can't find the issue? We use the epic name as its title pass # Create a new epic, cache it, and add it to the catalogue epicCache[epicName] = epic = Epic(epicName, epicTitle) catalogue.epics.append(epic) epic.stories.append(story) story.epic = epic acceptanceCriteria = singleValueCustomFieldForIssue(issue, acFieldId) try: appendScenarios(story, acceptanceCriteria) except ValueError, e: print "Error parsing acceptance criteria for", issue.key, "-", str(e) finally: client.service.logout(securityToken) return catalogue