Esempio n. 1
0
	def handle_comment(self, message):
		""" The user is posting a comment to Pivotal Tracker via email. """
		(sender, message_body, is_html, html_body, plain_body, subject) = self.parse_message(message)
		logging.info('is_html = %s', is_html)
		if is_html == True:
			# try to clean up the html
			message_body = self.strip_and_clean(message_body)

		user = db.Query(Users).filter('pt_emails =', sender).get()

		if user is None:
			self.log_and_reply(sender, "Could not find your Pivotal Tracker token. Have you signed up yet? " +
				"Your comment will not be added.\n\nYour original email:\n%s" % (message_body))
			return

		mytoken = user.pt_token

		story_id = self.get_story_id(message_body)

		if story_id == False:
			self.log_and_reply(sender,
				"Could not find the story Id. Your comment will not be added.\n\nYour original email:\n%s" % (
					message_body))
			return

		project_name = self.get_name_from_subject(subject)
		project_id = PTUtil.get_project_id(user, project_name, story_id)

		if project_id != False:
			logging.info("Using ProjectId: " + project_id + " StoryId: " + story_id)
		else:
			self.log_and_reply(sender,
				"Could not find the project for this story. Your comment will not be added.\n\nYour original email:\n%s" % (
					message_body))
			return

		comment = self.get_pt_comment(message_body, user.signatures, is_html)
		if comment is None:
			self.log_and_reply(sender, "Could not figure out what your comment was.\n\nYour original email:\n%s" % (
				message_body))
			return

		self.post_reply_to_pt(mytoken, project_id, story_id, comment)

		comment = Comments(user_id = user.user_id, project_id = project_id, story_id = story_id,
			comment = db.Text(comment))
		db.put(comment)
Esempio n. 2
0
	def guess_name_from_subject(self, user, subject):
		"""
		Looks through the users projects and tries to guess which one the subject is wanting. The subject should already
		be stripped down to the expected parts.
		Returns ([<closest_matches>], <damerau levenshtein value>)
		"""
		closest_match = ([], sys.maxint)

		project_names = PTUtil.get_project_names(user)
		if project_names == False:
			logging.error("couldn't get project names... croak!")
			return closest_match

		for project_name in project_names:
			distance = self.calc_word_distance(project_name.lower(), subject.lower())
			if distance == closest_match[1]:
				closest_match[0].append(project_name)
			elif distance < closest_match[1]:
				closest_match = ([project_name], distance)

		# TODO clear project cache and try again if no good match

		return closest_match
Esempio n. 3
0
	def new_ticket(self, message):
		""" The user is creating a new ticket in Pivotal Tracker via email. """
		(sender, message_body, is_html, html_body, plain_body, subject) = self.parse_message(message)
		logging.info('is_html = %s', is_html)
		if is_html == True:
			# try to clean up the html
			message_body = self.strip_and_clean(message_body)

		# clean up subject
		pattern = re.compile('^\s*re[\s:]+', re.I)
		subject = pattern.sub(lambda x: '', subject)

		# clean up message body
		pattern = re.compile('##### PT REPLY #####.*##### PT REPLY #####', re.I | re.S)
		message_body = pattern.sub(lambda x: ' ', message_body).strip()

		user = db.Query(Users).filter('pt_emails =', sender).get()

		if user is None:
			self.log_and_reply(sender,
				"##### PT REPLY #####\n" +
				"Could not find your Pivotal Tracker token. Have you signed up yet at ptreply.com? \n\n" +
				"Your story will not be added.\n" +
				"\nNote: this section will automatically be removed when you reply.\n" +
				"##### PT REPLY #####\n\n" + message_body)
			return

		token = user.pt_token

		bad_subject = False
		index = subject.find(':')
		if index < 0:
			# error no colon in subject
			bad_subject = True

		temp = subject[:index]
		story_name = subject[index+1:].strip()

		index = temp.rfind(' ')
		if index < 0:
			# error no space in subject
			bad_subject = True
			return

		if bad_subject == True:
			self.log_and_reply(sender,
				"##### PT REPLY #####\n" +
				"Your subject was confusing, please make sure it is in the following format:\n" +
				"  PROJECT_NAME STORY_TYPE: STORY_TITLE\n" +
				"  (Example: PT-MAIL bug: users can't login)\n" +
				"\nNote: this section will automatically be removed when you reply.\n" +
				"##### PT REPLY #####\n\n" + message_body, subject=subject, send_from=self.newreply, debug=False)
			return

		possible_project = temp[:index]
		story_type = temp[index+1:].lower()

		logging.info("possible_project: %s, story_type: %s", possible_project, story_type)

		projects, distance = self.guess_name_from_subject(user, possible_project)
		logging.info("projects = %s, distance = %s", projects, distance)

		if len(projects) == 0:
			# ERROR couldn't find any projects
			self.log_and_reply(sender,
				"##### PT REPLY #####\n" +
				"We couldn't find any projects in Pivotal Tracker that match your subject. " +
				"Please double check for typos in your subject and try again.\n" +
				"\nNote: this section will automatically be removed when you reply.\n" +
				"##### PT REPLY #####\n\n" + message_body, subject=subject, send_from=self.newreply, debug=False)
			return

		if len(projects) > 1 and distance == 0:
			# FATAL ERROR more than one project name matched EXACTLY, we cannot continue
			self.log_and_reply(sender,
				"##### PT REPLY #####\n" +
				"More than one project in Pivotal Tracker matched your subject EXACTLY.\n" +
				"\nThis is either because our guessing algorithm is wrong or you are actually a member " +
				"of more than one project with the same name. If you only have one project with this name, " +
				"please email us at [email protected] so we can take a look.\n" +
				"\nNote: this section will automatically be removed when you reply.\n" +
				"##### PT REPLY #####\n\n" + message_body, subject=subject, send_from=self.newreply, debug=False)
			return

		if len(projects) > 1 or distance > 2:
			# ERROR more than one project matched the same or not well enough, user needs to choose
			new_subject = "%s %s: %s" % (projects[0], story_type, story_name)

			self.log_and_reply(sender,
				"##### PT REPLY #####\n" +
				"We couldn't guess what project you wanted to add this story to, but we think we have a pretty good idea.\n" +
				"\nCheck the new subject of this email, and if it looks good, just hit reply and send and we'll take care of it.\n" +
				"\nHere are some other projects it might be, but you'll have to change the subject yourself:\n " +
				"\n ".join(projects[1:]) + "\n" +
				"\nNote: this section will automatically be removed when you reply.\n" +
				"##### PT REPLY #####\n\n" + message_body, subject=new_subject, send_from=self.newreply, debug=False)
			return

		project_id = PTUtil.get_project_id(user, projects[0])

		if project_id == False:
			self.log_and_reply(sender, "##### PT REPLY #####\n" +
				"Could not find the project in Pivotal Tracker.\n" +
				"\nPlease visit ptreply.com and verify that your token is still valid.\n" +
				"##### PT REPLY #####\n\n" + message_body)
			return

		# sanity check story type
		if story_type != 'feature' and story_type != 'bug' and story_type != 'release' and story_type != 'chore':
			story_type = 'feature'

		description = self.get_pt_comment(message_body, user.signatures, is_html)

		payload = """
			<story>
				<story_type>%s</story_type>
				<name>%s</name>
				<description>%s</description>
			</story>
			""" % (story_type, story_name, description)

		logging.info("Using project_id %s to post new %s: %s", project_id, story_type, story_name)
		logging.info("Payload: %s", payload)

		url = "https://www.pivotaltracker.com/services/v3/projects/%s/stories" % (project_id)
		result = urlfetch.fetch(url=url,
			payload=payload,
			method=urlfetch.POST,
			headers={'X-TrackerToken': token, 'Content-type': 'application/xml'})

		story_dom = minidom.parseString(result.content)
		story_id = None
		for node in story_dom.getElementsByTagName('story'):
			story_id = node.getElementsByTagName('id')[0].firstChild.data

		if story_id is not None:
			logging.info("Story Posted")
		else:
			logging.info("Failed to Post Story")
			logging.info(result.content)