def te_st_live(self): yt = Connection('https://tickets.i.gini.net', username, password) print 'connected to [%s]' % yt.baseUrl all_backend_issues = yt.getIssues('Backend', 'state:complete', 0, 1) print 'found %d issues' % len(all_backend_issues) changes = yt.get_changes_for_issue(all_backend_issues[0].id) state_changes = filter(has_state_changes, changes) self.assertEqual(4, len(state_changes)) open_state_changes = filter(partial(has_new_value, 'In Progress'), state_changes) self.assertEqual(1, len(open_state_changes)) open_state_time = open_state_changes[0].updated open_state_datetime = datetime.datetime.fromtimestamp(open_state_time / 1000.0) self.assertEqual(2016, open_state_datetime.year) self.assertEqual(7, open_state_datetime.month) self.assertEqual(6, open_state_datetime.day) resolved_state_changes = filter(has_resolved_value, state_changes) self.assertEqual(1, len(resolved_state_changes)) complete_state_time = resolved_state_changes[0].updated complete_state_datetime = datetime.datetime.fromtimestamp( complete_state_time / 1000.0) self.assertEqual(2016, complete_state_datetime.year) self.assertEqual(7, complete_state_datetime.month) self.assertEqual(13, complete_state_datetime.day) self.assertEqual(datetime.timedelta(7, 3875, 903000), complete_state_datetime - open_state_datetime)
def proyecto_elegido(bot, update, user_data): bot.sendChatAction(chat_id=update.callback_query.from_user.id, action=ChatAction.TYPING) # Es la primera vez que entra o cambia tipo de tareas? user_data['tipo_tarea'] = '#Unresolved' if not user_data.get('proyecto'): user_data['proyecto'] = update.callback_query.data logger.info('Elegir Proyecto Opción {}'.format(user_data['proyecto'])) else: user_data['tipo_tarea'] = update.callback_query.data logger.info('Elegir Proyecto Opción {} {}'.format( user_data['proyecto'], user_data['tipo_tarea'])) connection = Connection(user_data['host']['host'], user_data['host']['username'], user_data['host']['pass']) username, email = splitEmail(user_data['host']['username']) query = 'Type: Task and {} and ( Assignee: {} or #Unassigned )'.format( user_data['tipo_tarea'], username) issues = connection.getIssues(user_data['proyecto'], query, 0, 20) keyboard = [] texto = '*Tareas:* \n ' for issue in issues: texto += '\n *[{}]* _{}, {}_\n *Prioridad:* _{}_\n *Resumen:* {} \n'.format( issue['id'], issue['Type'], issue['State'], issue['Priority'], escapeMarkdown(utf8(issue['summary']))) keyboard.append( InlineKeyboardButton(issue['id'], callback_data=issue['id'])) # Agrego posibilidad de ver otras tareas if user_data['tipo_tarea'] == '#Unresolved': keyboard.append( InlineKeyboardButton('Ver solucionadas', callback_data='#Resolved')) else: keyboard.append( InlineKeyboardButton('Ver no solucionadas', callback_data='#Unresolved')) # Acomodo el teclado keyboard = [keyboard[i:i + 3] for i in range(0, len(keyboard), 3)] reply_markup = InlineKeyboardMarkup(keyboard, resize_keyboard=True, one_time_keyboard=True) texto += '\n *Elegí la tarea:*' if len(keyboard) > 0: update.callback_query.edit_message_text(text=texto, reply_markup=reply_markup, parse_mode='Markdown') return ISSUE else: update.callback_query.edit_message_text( text="No hay tareas asignadas a vos! Chau") return ConversationHandler.END
class ConnectionTest(unittest.TestCase): def setUp(self): #self.con = Connection('http://teamsys.intellij.net', 'resttest', 'resttest') self.con = Connection("http://localhost:8081", "root", "root") def test_getProject(self): p = self.con.getProject('SB') self.assertEqual(p.id, 'SB') self.assertEqual(p.name, 'Sandbox') def test_getSubsystems(self): subsystems = self.con.getSubsystems('SB') default = [s for s in subsystems if s.isDefault][0] self.assertTrue(default is not None) def test_getIssue(self): i = self.con.getIssue('SB-1') self.assertEqual(i.id, 'SB-1') self.assertEqual(i.numberInProject, '1') self.assertEqual(i.projectShortName, 'SB') def test_createIssue(self): i = self.con.createIssue('SB', 'resttest', 'Test issue', 'Test description', '2', 'Bug', 'First', 'Open', '', '', '') self.assertEqual(i.projectShortName, 'SB') self.assertEqual(i.priority, '2') self.assertEqual(i.type, 'Bug') self.assertEqual(i.subsystem, 'First') def test_createIssueAttachment(self): i = self.con.createIssue('SB', 'resttest', 'For attachmkents test', 'Test description', '2', 'Bug', 'First', 'Open', '', '', '') fname = 'connection_test.py' content = open(fname) self.con.createAttachment(i.id, fname, content) self.assertEqual(fname, self.con.getAttachments(i.id)[0].name) def test_createAndDeleteSubsystem(self): name = 'Test Subsystem [' + str(random.random()) + "]" self.con.createSubsystemDetailed('SB', name, False, 'resttest') s = self.con.getSubsystem('SB', name) self.assertEqual(s.name, name) self.assertEqual(s.isDefault, 'false') #todo: uncomment when fix deployed to teamsys #self.assertEqual(s.defaultAssignee, 'resttest') self.con.deleteSubsystem('SB', name) def test_importIssues(self): issues = self.con.getIssues("A", "", 0, 10) for issue in issues: if hasattr(issue, "Assignee"): issue["assigneeName"] = issue["Assignee"] del issue.Assignee self.con.importIssues("B", "assignees", issues)
def __init__(self, host, user, password, api_key): connection = Connection(url=host, api_key=api_key) # get one issue issue = connection.getIssue('PI-1000') # get first 10 issues in project JT for query 'for: me #unresolved' issues = connection.getIssues( 'pi', '#Resolved Fixed in build: {Next Build}', 0, 10) for issue in issues: print(issue)
class ConnectionTest(unittest.TestCase): def setUp(self): #self.con = Connection('http://teamsys.intellij.net', 'resttest', 'resttest') self.con = Connection("http://localhost:8081", "root", "root") def test_getProject(self): p = self.con.getProject('SB') self.assertEqual(p.id, 'SB') self.assertEqual(p.name, 'Sandbox') def test_getSubsystems(self): subsystems = self.con.getSubsystems('SB') default = [s for s in subsystems if s.isDefault][0] self.assertTrue(default is not None) def test_getIssue(self): i = self.con.getIssue('SB-1') self.assertEqual(i.id, 'SB-1') self.assertEqual(i.numberInProject, '1') self.assertEqual(i.projectShortName, 'SB') def test_createIssue(self): i = self.con.createIssue('SB', 'resttest', 'Test issue', 'Test description', '2', 'Bug', 'First', 'Open', '', '', '') self.assertEqual(i.projectShortName, 'SB') self.assertEqual(i.priority, '2') self.assertEqual(i.type, 'Bug') self.assertEqual(i.subsystem, 'First') def test_createIssueAttachment(self): i = self.con.createIssue('SB', 'resttest', 'For attachmkents test', 'Test description', '2', 'Bug', 'First', 'Open', '', '', '') fname = 'connection_test.py' content = open(fname) self.con.createAttachment(i.id, fname, content) self.assertEqual(fname, self.con.getAttachments(i.id)[0].name) def test_createAndDeleteSubsystem(self): name = 'Test Subsystem [' + str(random.random()) + "]" self.con.createSubsystemDetailed('SB', name, False, 'resttest') s = self.con.getSubsystem('SB', name) self.assertEqual(s.name, name) self.assertEqual(s.isDefault, 'false') #todo: uncomment when fix deployed to teamsys #self.assertEqual(s.defaultAssignee, 'resttest') self.con.deleteSubsystem('SB', name) def test_importIssues(self): issues = self.con.getIssues("A", "", 0, 10) for issue in issues: if hasattr(issue, "Assignee"): issue["assigneeName"] = issue["Assignee"] del issue.Assignee self.con.importIssues("B", "assignees", issues)
def issues_list_yt(request): yt = YouTrack('https://taptaxi.myjetbrains.com/youtrack/', token=ytt) issues = yt.getIssues( 'SUP', 'Тип: Обращение_клиента сортировать: создана по убыв.', '', '50') #for issue in issues: #issue.created = datetime.strptime(issue.created, '%Y-%m-%dT%H:%M:%S.%f%z') deps = Department.objects.filter().order_by('dep_name') return render(request, 'contacts/issues_yt.html', { 'issues': issues, 'deps': deps })
def import_attachments_only(source_url, source_login, source_password, target_url, target_login, target_password, project_ids): if not project_ids: print 'No projects to import. Exit...' return start = 0 max = 20 source = Connection(source_url, source_login, source_password) target = Connection(target_url, target_login, target_password) user_importer = UserImporter(source, target, caching_users=True) for projectId in project_ids: while True: try: print 'Get issues from %d to %d' % (start, start + max) issues = source.getIssues(projectId, '', start, max) if len(issues) <= 0: break for issue in issues: print 'Process attachments for issue %s' % issue.id attachments = issue.getAttachments() users = set([]) for a in attachments: author = a.getAuthor() if author is not None: users.add(author) user_importer.importUsersRecursively(users) for a in attachments: print 'Transfer attachment of %s: %s' % (issue.id, a.name.encode('utf-8')) try: target.createAttachmentFromAttachment(issue.id, a) except BaseException, e: print 'Cannot import attachment [ %s ]' % a.name.encode('utf-8') print repr(e) except Exception, e: print 'Cannot process issues from %d to %d' % (start, start + max) traceback.print_exc() raise e start += max
else: create_project_custom_field(target, field, projectId) # copy issues start = 0 max = 20 sync_workitems = True print "Import issues" while True: try: print "Get issues from " + str(start) + " to " + str(start + max) issues = source.getIssues(projectId, query, start, max) if len(issues) <= 0: break if convert_period_values and period_cf_names: for issue in issues: for pname in period_cf_names: for fname in issue.__dict__: if fname.lower() != pname: continue issue[fname] = period_to_minutes(issue[fname]) users = set([]) for issue in issues:
def import_attachments_only(source_url, source_login, source_password, target_url, target_login, target_password, project_ids, params=None): if not project_ids: print 'No projects to import. Exit...' return if params is None: params = {} start = 0 max = 20 source = Connection(source_url, source_login, source_password) target = Connection(target_url, target_login, target_password) user_importer = UserImporter(source, target, caching_users=params.get('enable_user_caching', True)) for projectId in project_ids: while True: try: print 'Get issues from %d to %d' % (start, start + max) issues = source.getIssues(projectId, '', start, max) if len(issues) <= 0: break for issue in issues: print 'Process attachments for issue %s' % issue.id existing_attachments = dict() try: for a in target.getAttachments(issue.id): existing_attachments[a.name + '\n' + a.created] = a except youtrack.YouTrackException, e: if e.response.status == 404: print "Skip importing attachments because issue %s doesn't exist" % issue.id continue raise e attachments = [] users = set([]) for a in issue.getAttachments(): if a.name + '\n' + a.created in existing_attachments and not params.get('replace_attachments'): print "Skip attachment '%s' (created: %s) because it's already exists" \ % (a.name.encode('utf-8'), a.created) continue attachments.append(a) author = a.getAuthor() if author is not None: users.add(author) user_importer.importUsersRecursively(users) for a in attachments: print 'Transfer attachment of %s: %s' % (issue.id, a.name.encode('utf-8')) try: target.createAttachmentFromAttachment(issue.id, a) except BaseException, e: print 'Cannot import attachment [ %s ]' % a.name.encode('utf-8') print repr(e) continue if params.get('replace_attachments'): try: old_attachment = existing_attachments.get(a.name + '\n' + a.created) if old_attachment: print 'Deleting old attachment' target.deleteAttachment(issue.id, old_attachment.id) except BaseException, e: print "Cannot delete attachment '%s' from issue %s" % (a.name.encode('utf-8'), issue.id) print e
create_project_custom_field(target, field, projectId) # copy issues start = 0 max = 20 sync_workitems = enable_time_tracking(source, target, projectId) tt_settings = target.getProjectTimeTrackingSettings(projectId) print "Import issues" last_created_issue_number = 0 while True: try: print "Get issues from " + str(start) + " to " + str(start + max) issues = source.getIssues(projectId, query, start, max) if len(issues) <= 0: break if convert_period_values and period_cf_names: for issue in issues: for pname in period_cf_names: for fname in issue.__dict__: if fname.lower() != pname: continue issue[fname] = period_to_minutes(issue[fname]) users = set([]) for issue in issues:
def Main(sendTo, subject, yamlMessage): """ Workflow Zabbix-YouTrack :param sendTo: URL to Youtrack (ex. https://youtrack.example.com) :param subject: subject from Zabbix Action :param yamlMessage: message from Zabbix Action :return: """ # ----- Use below example yamlMessage to debug ----- # yamlMessage = """Name: 'Test Zabbix-YT workflow, ignore it' # Text: 'Agent ping (server:agent.ping()): DOWN (1) ' # Hostname: 'server.exmpale.ru' # Status: "OK" # Severity: "High" # EventID: "96976" # TriggerID: "123456789012" """ messages = yaml.load(yamlMessage) # ----- START Issue parameters ----- # Correspondence between the YouTrackPriority and ZabbixSeverity # Critical >= High # Normal < High ytPriority = 'Normal' if messages['Severity'] == 'Disaster' or messages['Severity'] == 'High': ytPriority = 'Critical' ytName = "{} ZabbixTriggerID::{}".format(messages['Name'], messages['TriggerID']) # ----- END Issue parameters ----- # ----- START Youtrack Issue description ----- # Search link to other issue searchString = "Hostname: '{}'".format(messages['Hostname']) linkToHostIssue = "{youtrack}/issues/{projectname}?q={query}".format( youtrack=sendTo, projectname=YT_PROJECT_NAME, query=urllib.parse.quote(searchString, safe='') ) issueDescription = """ {ytName} ----- {yamlMessage} ----- - [https://zabbix.example.com/zabbix.php?action=dashboard.view Zabbix Dashboard] - Show [{linkToHostIssue} all issue for *this host*] """.format( ytName=ytName, yamlMessage=yamlMessage, linkToHostIssue=linkToHostIssue, ) # ----- END Youtrack Issue description ----- # ----- START Youtrack current week ----- # Create connect to Youtrack API connection = Connection(sendTo, YT_USER, YT_PASSWORD) # Get current week in YT format (Sprint planned) version = connection.getAllBundles('version') for fixVersion in version[0].values: if fixVersion['archived'] == False and fixVersion['released'] == False: fixVersionWeek = fixVersion['name'] break # ----- END Youtrack current week ----- # ----- START Youtrack get or create issue ----- # Get issue if exist # Search for TriggerID createNewIssue = False logger.debug("Get issue with text '{}'".format(messages['TriggerID'])) issue = connection.getIssues(YT_PROJECT_NAME, "ZabbixTriggerID::{}".format(messages['TriggerID']), 0, 1) if len(issue) == 0: createNewIssue = True else: # if issue contains TriggerID in summary, then it's good issue # else create new issue, this is bad issue, not from Zabbix if "ZabbixTriggerID::{}".format(messages['TriggerID']) in issue[0]['summary']: issueId = issue[0]['id'] issue = connection.getIssue(issueId) else: createNewIssue = True # Create new issue if createNewIssue: logger.debug("Create new issue because it is not exist") issue = connection.createIssue(YT_PROJECT_NAME, 'Unassigned', ytName, issueDescription, priority=ytPriority, subsystem=YT_SUBSYSTEM, type=YT_TYPE, ) time.sleep(3) # Parse ID for new issue result = re.search(r'(CM-\d*)', issue[0]['location']) issueId = result.group(0) issue = connection.getIssue(issueId) logger.debug("Issue have id={}".format(issueId)) # Set issue service ExecAndLog(connection, issueId, "Service {}".format(YT_SERVICE)) # Update priority ExecAndLog(connection, issueId, "Priority {}".format(ytPriority)) # ----- END Youtrack get or create issue ----- # ----- START PROBLEM block ------ if messages['Status'] == "PROBLEM": # Issue exist and NOT Hold on, Unnassigned and Estimated time set if issue['State'] != 'Hold on': # Estimated time ExecAndLog(connection, issueId, "Estimated time {}".format(YT_TIME)) # Update fix version ExecAndLog(connection=connection, issueId=issueId, command="Sprint planned {}".format(fixVersionWeek)) # Reopen if Fixed or Verified or Canceled if issue['State'] == 'Fixed' or issue['State'] == 'Verified' or issue['State'] == 'Canceled': # Reopen Issue ExecAndLog(connection, issueId, "State reopen") # Assignee issue ExecAndLog(connection, issueId, command="Assignee Unassigned") # Update summary and description for issue logger.debug("Run command in {issueId}: {command}".format(issueId=issueId, command="""Update summary and description with connection.updateIssue method""" )) connection.updateIssue(issueId=issueId, summary=ytName, description=issueDescription) # Add comment logger.debug("Run command in {issueId}: {command}".format(issueId=issueId, command="""Now is PROBLEM {}""".format( messages['Text']) )) connection.executeCommand(issueId=issueId, command="", comment=YT_COMMENT.format( status=messages['Status'], text=messages['Text']) ) # Send ID to Zabbix: logger.debug("ZABBIX-API: Send Youtrack ID to {}".format(messages['EventID'])) Zbx.event.acknowledge(eventids=messages['EventID'], message="Create Youtrack task") Zbx.event.acknowledge(eventids=messages['EventID'], message="https://youtrack.example.com/issue/{}".format(issueId)) # ----- End PROBLEM block ------ # ----- Start OK block ----- if messages['Status'] == "OK": if issue['State'] == 'Hold on' or issue['State'] == 'Registered': # Cancel if not in work ExecAndLog(connection, issueId, command="State Cancel") # Assignee issue ExecAndLog(connection, issueId, command="Assignee {}".format(YT_ASSIGNEE)) if issue['State'] == 'Fixed': # Verify if Fixed ExecAndLog(connection, issueId, command="State verify") logger.debug("Run command in {issueId}: {command}".format(issueId=issueId, command="""Now is OK {}""".format(messages['Text']) )) connection.executeCommand(issueId=issueId, command="", comment=YT_COMMENT.format( status=messages['Status'], text=messages['Text']) )
REDMINE_URL = config.get('REDMINE', 'url') REDMINE_API_KEY = config.get('REDMINE', 'api key') REDMINE_PROJECT = config.get('REDMINE', 'project') YOUTRACK_PROJECT = config.get('YOUTRACK', 'project') SEARCH_QUERY = config.get('YOUTRACK', 'search query').replace('\n', '') try: youtrack = Connection(YOUTRACK_URL, LOGIN, PASSWRD) except YouTrackException as yt_exc: print yt_exc sys.exit(0) redmine = Redmine(REDMINE_URL, key=REDMINE_API_KEY, requests={'verify': False}) print "Getting YouTrack issues..." issues_list = youtrack.getIssues('project: ' + YOUTRACK_PROJECT + ' ' + SEARCH_QUERY, 0, 5000) try: redmine_users = get_redmine_users() redmine_trackers = get_redmine_trackers() redmine_statuses = get_redmine_statuses() redmine_priorities = get_redmine_priorities() redmine_versions = get_redmine_project_versions() redmine_custom_fields = get_redmine_custom_fields() except Exception as e: print e sys.exit(0) issues_dict = {} print "==========Copying from YouTrack to Redmine=========="
class YouTrackExporter(object): def __init__(self, yt, influx): self.yt = YouTrack(**yt) self.influx = InfluxDBClient(**influx) self.logger = logging.getLogger('worktime_reporter') self.project_blacklist = ['RRS', 'RR_INS'] def get_all_issues(self, project): def _gen(): skip = 0 while True: issues = self.yt.getIssues(project.id, 'Spent time: -?', skip, 50) if not issues: break skip += len(issues) yield issues return itertools.chain.from_iterable(_gen()) def issue_to_measurement(self, project, issue): self.logger.info(f'Looking into issue {issue.id}') lead_time = getattr(issue, 'Lead time', None) stream = getattr(issue, 'Stream', None) target = getattr(issue, 'Target', 'Core') _type = getattr(issue, 'Type', None) resolved = getattr(issue, 'resolved', None) estimation = getattr(issue, 'Estimation', None) tags = { 'project': project.id, 'issue': issue.id, 'stream': stream, 'target': target, 'type': _type } counter = defaultdict(lambda: 0) for work_item in self.yt.getWorkItems(issue.id): _tags = dict(tags) _tags['author'] = getattr(work_item, 'authorLogin', None) created = getattr(work_item, 'created', None) work_type = getattr(work_item, 'worktype', None) date = getattr(work_item, 'date', created) duration = int(work_item.duration) if not created: continue created_dt = datetime.datetime.fromtimestamp(float(created)/1000) date_dt = datetime.datetime.fromtimestamp(float(date)/1000) time_dt = datetime.datetime.combine(date_dt.date(), created_dt.time(), date_dt.tzinfo) time_ts = int(datetime.datetime.timestamp(time_dt)*1000) counter[work_type] += duration yield { 'measurement': 'work_item', 'tags': _tags, 'time': time_ts, 'fields': { work_type: duration } } cycle_time = counter['Analytics'] + counter['Development'] + counter['Testing'] created = getattr(issue, 'created', None) if estimation: yield { 'measurement': 'issue', 'tags': tags, 'time': int(created), 'fields': { 'estimation': int(estimation) } } if lead_time and resolved: yield { 'measurement': 'issue', 'tags': tags, 'time': int(resolved), 'fields': { 'lead_time': float(lead_time), 'cycle_time': cycle_time, } } def process_project(self, project): if project.id in self.project_blacklist: self.logger.info(f'Skipping project {project.id}, blacklisted') return ts = self.yt.getProjectTimeTrackingSettings(project['id']) if not ts.Enabled: self.logger.info( f'Skipping project {project.id}, no time tracking') return self.logger.info(f'Looking into project {project.id}') issues = self.get_all_issues(project) measurements = (self.issue_to_measurement(project, issue) for issue in issues) return itertools.chain.from_iterable(measurements) def export(self): def _gen(): projects = self.yt.getProjects() for project_id in projects: project = self.yt.getProject(project_id) measurements = self.process_project(project) if measurements: yield measurements self.influx.drop_measurement('issue') self.influx.drop_measurement('work_item') measurements = itertools.chain.from_iterable(_gen()) for chunk in grouper(measurements, 50): filtered = filter(lambda x: x is not None, chunk) self.influx.write_points(filtered, time_precision='ms')
def proyecto_elegido(bot, update, user_data): bot.sendChatAction(chat_id=update.callback_query.from_user.id, action=ChatAction.TYPING) # Es la primera vez que entra o cambia tipo de tareas? if not user_data.get('proyecto'): user_data['proyecto'] = update.callback_query.data user_data['pagging'] = [0, max_issues_per_page] user_data['tipo_tarea'] = '#{Sin resolver}' logger.info('Elegir Proyecto Opción {}'.format(user_data['proyecto'])) elif update.callback_query.data == '>': user_data['pagging'] = [ user_data['pagging'][0] + max_issues_per_page, user_data['pagging'][1] + max_issues_per_page ] elif update.callback_query.data == '<': user_data['pagging'] = [ user_data['pagging'][0] - max_issues_per_page, user_data['pagging'][1] - max_issues_per_page ] else: user_data['tipo_tarea'] = update.callback_query.data user_data['pagging'] = [0, max_issues_per_page] logger.info('Elegir Proyecto Opción {} {}'.format( user_data['proyecto'], user_data['tipo_tarea'])) logger.info('paginas {}/{}'.format(user_data['pagging'][0], user_data['pagging'][1])) connection = Connection(user_data['host']['host'], user_data['host']['username'], user_data['host']['pass']) username, email = splitEmail(user_data['host']['username']) #of #me #{Sin asignar} -Resolved query = '(asignado a: ' + username + ' o #{Sin asignar}) y ' + user_data[ 'tipo_tarea'] logger.info(query) issues = connection.getIssues(user_data['proyecto'], query, user_data['pagging'][0], max_issues_per_page) #Necesito guardar el numero de issues segun query para el paginado pq es lento if user_data['tipo_tarea'] == '#resuelta': if not user_data.get('issue_count_resueltas'): user_data['issue_count_resueltas'] = connection.getNumberOfIssues( query + ' y #' + user_data['proyecto']) issue_count = user_data['issue_count_resueltas'] else: if not user_data.get('issue_count_no_resueltas'): user_data[ 'issue_count_no_resueltas'] = connection.getNumberOfIssues( query + ' y #' + user_data['proyecto']) issue_count = user_data['issue_count_no_resueltas'] keyboard = [] texto = '*Tareas:* \n ' for issue in issues: texto += '\n *[{}]* _{}, {}_\n *Prioridad:* _{}_\n *Resumen:* {} \n'.format( issue['id'], issue['Type'], issue['State'], issue['Priority'], escapeMarkdown(utf8(issue['summary']))) keyboard.append( InlineKeyboardButton(issue['id'], callback_data=issue['id'])) # Agrego posibilidad de ver otras tareas if user_data['tipo_tarea'] == '#{Sin resolver}': keyboard.append( InlineKeyboardButton('Ver solucionadas', callback_data='#resuelta')) else: keyboard.append( InlineKeyboardButton('Ver no solucionadas', callback_data='#{Sin resolver}')) #Paginado if user_data['pagging'][0] > 0: keyboard.append(InlineKeyboardButton('<', callback_data='<')) if len(issues) >= 5: keyboard.append(InlineKeyboardButton('>', callback_data='>')) # Acomodo el teclado keyboard = [keyboard[i:i + 3] for i in range(0, len(keyboard), 3)] reply_markup = InlineKeyboardMarkup(keyboard, resize_keyboard=True, one_time_keyboard=True) texto += '\n *Elegí la tarea:*' if len(keyboard) > 0: update.callback_query.edit_message_text(text=texto, reply_markup=reply_markup, parse_mode='Markdown') return ISSUE else: update.callback_query.edit_message_text( text="No hay tareas asignadas a vos! Chau") return ConversationHandler.END
def Main(sendTo, subject, yamlMessage): """ Workflow Zabbix-YouTrack :param sendTo: URL to Youtrack (ex. https://youtrack.example.com) :param subject: subject from Zabbix Action :param yamlMessage: message from Zabbix Action :return: """ # ----- Use below example yamlMessage to debug ----- # yamlMessage = """Name: 'Test Zabbix-YT workflow, ignore it' # Text: 'Agent ping (server:agent.ping()): DOWN (1) ' # Hostname: 'server.exmpale.ru' # Status: "OK" # Severity: "High" # EventID: "96976" # TriggerID: "123456789012" """ messages = yaml.load(yamlMessage) # ----- START Issue parameters ----- # Correspondence between the YouTrackPriority and ZabbixSeverity # Critical >= High # Normal < High ytPriority = 'Normal' if messages['Severity'] == 'Disaster' or messages['Severity'] == 'High': ytPriority = 'Critical' ytName = "{} ZabbixTriggerID::{}".format(messages['Name'], messages['TriggerID']) # ----- END Issue parameters ----- # ----- START Youtrack Issue description ----- # Search link to other issue searchString = "Hostname: '{}'".format(messages['Hostname']) linkToHostIssue = "{youtrack}/issues/{projectname}?q={query}".format( youtrack=sendTo, projectname=YT_PROJECT_NAME, query=urllib.parse.quote(searchString, safe='')) issueDescription = """ {ytName} ----- {yamlMessage} ----- - [https://zabbix.example.com/zabbix.php?action=dashboard.view Zabbix Dashboard] - Show [{linkToHostIssue} all issue for *this host*] """.format( ytName=ytName, yamlMessage=yamlMessage, linkToHostIssue=linkToHostIssue, ) # ----- END Youtrack Issue description ----- # ----- START Youtrack current week ----- # Create connect to Youtrack API connection = Connection(sendTo, YT_USER, YT_PASSWORD) # Get current week in YT format (Sprint planned) version = connection.getAllBundles('version') for fixVersion in version[0].values: if fixVersion['archived'] == False and fixVersion['released'] == False: fixVersionWeek = fixVersion['name'] break # ----- END Youtrack current week ----- # ----- START Youtrack get or create issue ----- # Get issue if exist # Search for TriggerID createNewIssue = False logger.debug("Get issue with text '{}'".format(messages['TriggerID'])) issue = connection.getIssues( YT_PROJECT_NAME, "ZabbixTriggerID::{}".format(messages['TriggerID']), 0, 1) if len(issue) == 0: createNewIssue = True else: # if issue contains TriggerID in summary, then it's good issue # else create new issue, this is bad issue, not from Zabbix if "ZabbixTriggerID::{}".format( messages['TriggerID']) in issue[0]['summary']: issueId = issue[0]['id'] issue = connection.getIssue(issueId) else: createNewIssue = True # Create new issue if createNewIssue: logger.debug("Create new issue because it is not exist") issue = connection.createIssue( YT_PROJECT_NAME, 'Unassigned', ytName, issueDescription, priority=ytPriority, subsystem=YT_SUBSYSTEM, type=YT_TYPE, ) time.sleep(3) # Parse ID for new issue result = re.search(r'(CM-\d*)', issue[0]['location']) issueId = result.group(0) issue = connection.getIssue(issueId) logger.debug("Issue have id={}".format(issueId)) # Set issue service ExecAndLog(connection, issueId, "Service {}".format(YT_SERVICE)) # Update priority ExecAndLog(connection, issueId, "Priority {}".format(ytPriority)) # ----- END Youtrack get or create issue ----- # ----- START PROBLEM block ------ if messages['Status'] == "PROBLEM": # Issue exist and NOT Hold on, Unnassigned and Estimated time set if issue['State'] != 'Hold on': # Estimated time ExecAndLog(connection, issueId, "Estimated time {}".format(YT_TIME)) # Update fix version ExecAndLog(connection=connection, issueId=issueId, command="Sprint planned {}".format(fixVersionWeek)) # Reopen if Fixed or Verified or Canceled if issue['State'] == 'Fixed' or issue['State'] == 'Verified' or issue[ 'State'] == 'Canceled': # Reopen Issue ExecAndLog(connection, issueId, "State reopen") # Assignee issue ExecAndLog(connection, issueId, command="Assignee Unassigned") # Update summary and description for issue logger.debug("Run command in {issueId}: {command}".format( issueId=issueId, command= """Update summary and description with connection.updateIssue method""" )) connection.updateIssue(issueId=issueId, summary=ytName, description=issueDescription) # Add comment logger.debug("Run command in {issueId}: {command}".format( issueId=issueId, command="""Now is PROBLEM {}""".format(messages['Text']))) connection.executeCommand(issueId=issueId, command="", comment=YT_COMMENT.format( status=messages['Status'], text=messages['Text'])) # Send ID to Zabbix: logger.debug("ZABBIX-API: Send Youtrack ID to {}".format( messages['EventID'])) Zbx.event.acknowledge(eventids=messages['EventID'], message="Create Youtrack task") Zbx.event.acknowledge( eventids=messages['EventID'], message="https://youtrack.example.com/issue/{}".format(issueId)) # ----- End PROBLEM block ------ # ----- Start OK block ----- if messages['Status'] == "OK": if issue['State'] == 'Hold on' or issue['State'] == 'Registered': # Cancel if not in work ExecAndLog(connection, issueId, command="State Cancel") # Assignee issue ExecAndLog(connection, issueId, command="Assignee {}".format(YT_ASSIGNEE)) if issue['State'] == 'Fixed': # Verify if Fixed ExecAndLog(connection, issueId, command="State verify") logger.debug("Run command in {issueId}: {command}".format( issueId=issueId, command="""Now is OK {}""".format(messages['Text']))) connection.executeCommand(issueId=issueId, command="", comment=YT_COMMENT.format( status=messages['Status'], text=messages['Text']))
try: passwd_encoded = Config.get('USER', 'PASSWORD') passwd = base64.b64decode(passwd_encoded) except ConfigParser.NoOptionError: passwd = getpass.getpass('Password is not set! Type password: '******'USER', 'PASSWORD', passwd_encoded) with open('config', 'w') as cfg_file: Config.write(cfg_file) try: connection = Connection(server, domain + '\\' + user, passwd) # Get array of issues issues_list = connection.getIssues( project, 'Category: {' + category + '} State: -Obsolete sort by: {issue id} asc', 0, 2000) doc = minidom.Document() root = doc.createElement('sections') def get_super_parents(issues): parent_dict = dict() for issue in issues: parent_dict[issue.id] = (connection.getIssues( project, 'Category: {' + category + '} State: -Obsolete Parent for: ' + issue.id, 0, 2000)) parents = [] for s in range(len(parent_dict.items())): if not parent_dict.items()[s][1]: parents.append(parent_dict.items()[s][0])
REDMINE_PROJECT = config.get('REDMINE', 'project') YOUTRACK_PROJECT = config.get('YOUTRACK', 'project') SEARCH_QUERY = config.get('YOUTRACK', 'search query').replace('\n', '') try: youtrack = Connection(YOUTRACK_URL, LOGIN, PASSWRD) except YouTrackException as yt_exc: print yt_exc sys.exit(0) redmine = Redmine(REDMINE_URL, key=REDMINE_API_KEY, requests={'verify': False}) print "Getting YouTrack issues..." issues_list = youtrack.getIssues( 'project: ' + YOUTRACK_PROJECT + ' ' + SEARCH_QUERY, 0, 5000) try: redmine_users = get_redmine_users() redmine_trackers = get_redmine_trackers() redmine_statuses = get_redmine_statuses() redmine_priorities = get_redmine_priorities() redmine_versions = get_redmine_project_versions() redmine_custom_fields = get_redmine_custom_fields() except Exception as e: print e sys.exit(0) issues_dict = {} print "==========Copying from YouTrack to Redmine=========="
parentdir = os.path.dirname(os.path.abspath(__file__)) libdir = parentdir + '/youtrack/' sys.path.append(libdir) from youtrack.connection import Connection passwd = open('password.txt', 'r').read().strip() user = config.USERNAME server = 'https://106.125.46.213/youtrack/' connection = Connection(server, user, passwd) category = config.CATEGORY print category + ' mindmap creation...' # Get array of issues issues_list = connection.getIssues('TRAR', 'Category: ' + category + ' State: -Obsolete sort by: {issue id} asc', 0, 2000) issue_map = xmind.load(category + ".xmind") sheet = issue_map.getPrimarySheet() sheet.setTitle(category) root_primary = sheet.getRootTopic() root_primary.setTitle(category) def getSuperParents(issues): parent_dict = dict() for issue in issues: parent_dict[issue.id] = (connection.getIssues('TRAR', 'Category: ' + category + ' State: -Obsolete Parent for: ' + issue.id, 0, 2000)) parents = [] for s in range(len(parent_dict.items())): if not parent_dict.items()[s][1]:
user = Config.get('USER', 'Username') try: passwd_encoded = Config.get('USER', 'PASSWORD') passwd = base64.b64decode(passwd_encoded) except ConfigParser.NoOptionError: passwd = getpass.getpass('Password is not set! Type password: '******'USER', 'PASSWORD', passwd_encoded) with open('config', 'w') as cfg_file: Config.write(cfg_file) try: connection = Connection(server, domain + '\\' + user, passwd) # Get array of issues issues_list = connection.getIssues(project, 'Category: {' + category + '} State: -Obsolete sort by: {issue id} asc', 0, 2000) doc = minidom.Document() root = doc.createElement('sections') def get_super_parents(issues): parent_dict = dict() for issue in issues: parent_dict[issue.id] = (connection.getIssues(project, 'Category: {' + category + '} State: -Obsolete Parent for: ' + issue.id, 0, 2000)) parents = [] for s in range(len(parent_dict.items())): if not parent_dict.items()[s][1]: parents.append(parent_dict.items()[s][0]) return parents def get_subtasks(issue_id):
def youtrack2youtrack(source_url, source_login, source_password, target_url, target_login, target_password, project_ids, query='', source_token=None, target_token=None, params=None): if not len(project_ids): print("You should sign at least one project to import") return if params is None: params = {} source = Connection(source_url, source_login, source_password) if (source_token is None) else Connection(source_url, token=source_token) target = Connection(target_url, target_login, target_password) if (target_token is None) else Connection(target_url, token=target_token) # , proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 8888) print("Import issue link types") for ilt in source.getIssueLinkTypes(): try: print(target.createIssueLinkType(ilt)) except youtrack.YouTrackException as e: print(e.message) user_importer = UserImporter(source, target, caching_users=params.get('enable_user_caching', True)) link_importer = LinkImporter(target) # create all projects with minimum info and project lead set created_projects = [] for project_id in project_ids: created = create_project_stub(source, target, project_id, user_importer) created_projects.append(created) # save created project ids to create correct group roles afterwards user_importer.addCreatedProjects([project.id for project in created_projects]) # import project leads with group they are included and roles assigned to these groups user_importer.importUsersRecursively([target.getUser(project.lead) for project in created_projects]) # afterwards in a script any user import imply recursive import cf_names_to_import = set([]) # names of cf prototypes that should be imported for project_id in project_ids: cf_names_to_import.update([pcf.name.capitalize() for pcf in source.getProjectCustomFields(project_id)]) target_cf_names = [pcf.name.capitalize() for pcf in target.getCustomFields()] period_cf_names = [] for cf_name in cf_names_to_import: source_cf = source.getCustomField(cf_name) if source_cf.type.lower() == 'period': period_cf_names.append(source_cf.name.lower()) print("Processing custom field '%s'" % utf8encode(cf_name)) if cf_name in target_cf_names: target_cf = target.getCustomField(cf_name) if not (target_cf.type == source_cf.type): print("In your target and source YT instances you have field with name [ %s ]" % utf8encode(cf_name)) print("They have different types. Source field type [ %s ]. Target field type [ %s ]" % (source_cf.type, target_cf.type)) print("exiting...") exit() else: if hasattr(source_cf, "defaultBundle"): create_bundle_from_bundle(source, target, source_cf.defaultBundle, source_cf.type, user_importer) target.createCustomField(source_cf) failed_commands = [] for projectId in project_ids: source = Connection(source_url, source_login, source_password) if (source_token is None) else Connection( source_url, token=source_token) target = Connection(target_url, target_login, target_password) if (target_token is None) else Connection( target_url, token=target_token) # , proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 8888) # reset connections to avoid disconnections user_importer.resetConnections(source, target) link_importer.resetConnections(target) # copy project, subsystems, versions project = source.getProject(projectId) link_importer.addAvailableIssuesFrom(projectId) project_custom_fields = source.getProjectCustomFields(projectId) # create bundles and additional values for pcf_ref in project_custom_fields: pcf = source.getProjectCustomField(projectId, pcf_ref.name) if hasattr(pcf, "bundle"): try: create_bundle_from_bundle(source, target, pcf.bundle, source.getCustomField(pcf.name).type, user_importer) except youtrack.YouTrackException as e: if e.response.status != 409: raise e else: print(e) target_project_fields = [pcf.name.lower() for pcf in target.getProjectCustomFields(projectId)] for field in project_custom_fields: if field.name.lower() in target_project_fields: if hasattr(field, 'bundle'): if field.bundle != target.getProjectCustomField(projectId, field.name).bundle: target.deleteProjectCustomField(projectId, field.name) create_project_custom_field(target, field, projectId) else: try: create_project_custom_field(target, field, projectId) except youtrack.YouTrackException as e: if e.response.status != 409: raise e else: print(e) # copy issues start = 0 max = 20 sync_workitems = enable_time_tracking(source, target, projectId) tt_settings = target.getProjectTimeTrackingSettings(projectId) print("Import issues") last_created_issue_number = 0 while True: try: print("Get issues from " + str(start) + " to " + str(start + max)) issues = source.getIssues(projectId, query, start, max) if len(issues) <= 0: break if convert_period_values and period_cf_names: for issue in issues: for pname in period_cf_names: for fname in issue.__dict__: if fname.lower() != pname: continue issue[fname] = period_to_minutes(issue[fname]) users = set([]) for issue in issues: print("Collect users for issue [%s]" % issue.id) users.add(issue.getReporter()) if issue.hasAssignee(): if isinstance(issue.Assignee, (list, tuple)): users.update(issue.getAssignee()) else: users.add(issue.getAssignee()) # TODO: http://youtrack.jetbrains.net/issue/JT-6100 users.add(issue.getUpdater()) if issue.hasVoters(): users.update(issue.getVoters()) for comment in issue.getComments(): users.add(comment.getAuthor()) print("Collect links for issue [%s]" % issue.id) link_importer.collectLinks(issue.getLinks(True)) # links.extend(issue.getLinks(True)) # fix problem with comment.text for comment in issue.getComments(): if not hasattr(comment, "text") or (len(comment.text.strip()) == 0): setattr(comment, 'text', 'no text') user_importer.importUsersRecursively(users) print("Create issues [" + str(len(issues)) + "]") if params.get('create_new_issues'): create_issues(target, issues, last_created_issue_number) else: print(target.importIssues(projectId, project.name + ' Assignees', issues)) link_importer.addAvailableIssues(issues) for issue in issues: try: target_issue = target.getIssue(issue.id) except youtrack.YouTrackException as e: print("Cannot get target issue") print(e) continue if params.get('sync_tags') and issue.tags: try: for tag in issue.tags: tag = re.sub(r'[,&<>]', '_', tag) try: target.executeCommand(issue.id, 'tag ' + tag, disable_notifications=True) except youtrack.YouTrackException: tag = re.sub(r'[\s-]', '_', tag) target.executeCommand(issue.id, 'tag ' + tag, disable_notifications=True) except youtrack.YouTrackException as e: print("Cannot sync tags for issue " + issue.id) print(e) if params.get('add_new_comments'): target_comments = dict() max_id = 0 for c in target_issue.getComments(): target_comments[c.created] = c if max_id < c.created: max_id = c.created for c in issue.getComments(): if c.created > max_id or c.created not in target_comments: group = None if hasattr(c, 'permittedGroup'): group = c.permittedGroup try: target.executeCommand(issue.id, 'comment', c.text, group, c.author, disable_notifications=True) except youtrack.YouTrackException as e: print('Cannot add comment to issue') print(e) if params.get('sync_custom_fields'): skip_fields = [] if tt_settings and tt_settings.Enabled and tt_settings.TimeSpentField: skip_fields.append(tt_settings.TimeSpentField) skip_fields = [name.lower() for name in skip_fields] for pcf in [pcf for pcf in project_custom_fields if pcf.name.lower() not in skip_fields]: target_cf_value = None if pcf.name in target_issue: target_cf_value = target_issue[pcf.name] if isinstance(target_cf_value, (list, tuple)): target_cf_value = set(target_cf_value) elif target_cf_value == target.getProjectCustomField(projectId, pcf.name).emptyText: target_cf_value = None source_cf_value = None if pcf.name in issue: source_cf_value = issue[pcf.name] if isinstance(source_cf_value, (list, tuple)): source_cf_value = set(source_cf_value) elif source_cf_value == source.getProjectCustomField(projectId, pcf.name).emptyText: source_cf_value = None if source_cf_value == target_cf_value: continue if isinstance(source_cf_value, set) or isinstance(target_cf_value, set): if source_cf_value is None: source_cf_value = set([]) elif not isinstance(source_cf_value, set): source_cf_value = set([source_cf_value]) if target_cf_value is None: target_cf_value = set([]) elif not isinstance(target_cf_value, set): target_cf_value = set([target_cf_value]) for v in target_cf_value: if v not in source_cf_value: target.executeCommand(issue.id, 'remove %s %s' % (pcf.name, v), disable_notifications=True) for v in source_cf_value: if v not in target_cf_value: target.executeCommand(issue.id, 'add %s %s' % (pcf.name, v), disable_notifications=True) else: if source_cf_value is None: source_cf_value = target.getProjectCustomField(projectId, pcf.name).emptyText if pcf.type.lower() == 'date': m = re.match(r'(\d{10})(?:\d{3})?', str(source_cf_value)) if m: source_cf_value = datetime.datetime.fromtimestamp( int(m.group(1))).strftime('%Y-%m-%d') elif pcf.type.lower() == 'period': source_cf_value = '%sm' % source_cf_value command = '%s %s' % (pcf.name, source_cf_value) try: target.executeCommand(issue.id, command, disable_notifications=True) except youtrack.YouTrackException as e: if e.response.status == 412 and e.response.reason.find('Precondition Failed') > -1: print('WARN: Some workflow blocks following command: %s' % command) failed_commands.append((issue.id, command)) if sync_workitems: workitems = source.getWorkItems(issue.id) if workitems: existing_workitems = dict() target_workitems = target.getWorkItems(issue.id) if target_workitems: for w in target_workitems: _id = '%s\n%s\n%s' % (w.date, w.authorLogin, w.duration) if hasattr(w, 'description'): _id += '\n%s' % w.description existing_workitems[_id] = w new_workitems = [] for w in workitems: _id = '%s\n%s\n%s' % (w.date, w.authorLogin, w.duration) if hasattr(w, 'description'): _id += '\n%s' % w.description if _id not in existing_workitems: new_workitems.append(w) if new_workitems: print("Process workitems for issue [ " + issue.id + "]") try: user_importer.importUsersRecursively( [source.getUser(w.authorLogin) for w in new_workitems]) target.importWorkItems(issue.id, new_workitems) except youtrack.YouTrackException as e: if e.response.status == 404: print("WARN: Target YouTrack doesn't support workitems importing.") print("WARN: Workitems won't be imported.") sync_workitems = False else: print("ERROR: Skipping workitems because of error:" + str(e)) print("Process attachments for issue [%s]" % issue.id) existing_attachments = dict() try: for a in target.getAttachments(issue.id): existing_attachments[a.name + '\n' + a.created] = a except youtrack.YouTrackException as e: if e.response.status == 404: print("Skip importing attachments because issue %s doesn't exist" % issue.id) continue raise e attachments = [] users = set([]) for a in issue.getAttachments(): if a.name + '\n' + a.created in existing_attachments and not params.get('replace_attachments'): a.name = utf8encode(a.name) try: print("Skip attachment '%s' (created: %s) because it's already exists" % (utf8encode(a.name), utf8encode(a.created))) except Exception: pass continue attachments.append(a) author = a.getAuthor() if author is not None: users.add(author) user_importer.importUsersRecursively(users) for a in attachments: print("Transfer attachment of " + utf8encode(issue.id) + ": " + utf8encode(a.name)) # TODO: add authorLogin to workaround http://youtrack.jetbrains.net/issue/JT-6082 # a.authorLogin = target_login try: target.createAttachmentFromAttachment(issue.id, a) except BaseException as e: print("Cant import attachment [ %s ]" % utf8encode(a.name)) print(repr(e)) continue if params.get('replace_attachments'): try: old_attachment = existing_attachments.get(a.name + '\n' + a.created) if old_attachment: print('Deleting old attachment') target.deleteAttachment(issue.id, old_attachment.id) except BaseException as e: print("Cannot delete attachment '%s' from issue %s" % ( utf8encode(a.name), utf8encode(issue.id))) print(e) except Exception as e: print('Cant process issues from ' + str(start) + ' to ' + str(start + max)) traceback.print_exc() raise e start += max print("Import issue links") link_importer.importCollectedLinks() print("Trying to execute failed commands once again") for issue_id, command in failed_commands: try: print('Executing command on issue %s: %s' % (issue_id, command)) target.executeCommand(issue_id, command, disable_notifications=True) except youtrack.YouTrackException as e: print('Failed to execute command for issue #%s: %s' % (issue_id, command)) print(e)
def import_attachments_only(source_url, source_login, source_password, target_url, target_login, target_password, project_ids, source_token=None, target_token=None, params=None): if not project_ids: print('No projects to import. Exit...') return if params is None: params = {} start = 0 max = 20 source = Connection(source_url, source_login, source_password) if (source_token is None) else Connection(source_url, token=source_token) target = Connection(target_url, target_login, target_password) if (target_token is None) else Connection(target_url, token=target_token) user_importer = UserImporter(source, target, caching_users=params.get('enable_user_caching', True)) for projectId in project_ids: while True: try: print('Get issues from %d to %d' % (start, start + max)) issues = source.getIssues(projectId, '', start, max) if len(issues) <= 0: break for issue in issues: print('Process attachments for issue %s' % issue.id) existing_attachments = dict() try: for a in target.getAttachments(issue.id): existing_attachments[a.name + '\n' + a.created] = a except youtrack.YouTrackException as e: if e.response.status == 404: print("Skip importing attachments because issue %s doesn't exist" % issue.id) continue raise e attachments = [] users = set([]) for a in issue.getAttachments(): if a.name + '\n' + a.created in existing_attachments and not params.get('replace_attachments'): print("Skip attachment '%s' (created: %s) because it's already exists" % (utf8encode(a.name), utf8encode(a.created))) continue attachments.append(a) author = a.getAuthor() if author is not None: users.add(author) user_importer.importUsersRecursively(users) for a in attachments: print('Transfer attachment of %s: %s' % (utf8encode(issue.id), utf8encode(a.name))) try: target.createAttachmentFromAttachment(issue.id, a) except BaseException as e: print('Cannot import attachment [ %s ]' % utf8encode(a.name)) print(repr(e)) continue if params.get('replace_attachments'): try: old_attachment = existing_attachments.get(a.name + '\n' + a.created) if old_attachment: print('Deleting old attachment') target.deleteAttachment(issue.id, old_attachment.id) except BaseException as e: print("Cannot delete attachment '%s' from issue %s" % ( utf8encode(a.name), utf8encode(issue.id))) print(e) except Exception as e: print('Cannot process issues from %d to %d' % (start, start + max)) traceback.print_exc() raise e start += max
libdir = parentdir + '/youtrack/' sys.path.append(libdir) from youtrack.connection import Connection passwd = open('password.txt', 'r').read().strip() user = config.USERNAME server = 'https://106.125.46.213/youtrack/' connection = Connection(server, user, passwd) category = config.CATEGORY print category + ' mindmap creation...' # Get array of issues issues_list = connection.getIssues( 'TRAR', 'Category: ' + category + ' State: -Obsolete sort by: {issue id} asc', 0, 2000) issue_map = xmind.load(category + ".xmind") sheet = issue_map.getPrimarySheet() sheet.setTitle(category) root_primary = sheet.getRootTopic() root_primary.setTitle(category) def getSuperParents(issues): parent_dict = dict() for issue in issues: parent_dict[issue.id] = (connection.getIssues( 'TRAR', 'Category: ' + category + ' State: -Obsolete Parent for: ' + issue.id, 0, 2000))