class ZendeskAction(Action): def __init__(self, config): super(ZendeskAction, self).__init__(config=config) self.email = self.config['email'] self.token = self.config['api_token'] self.subdomain = self.config['subdomain'] self.credentials = { 'email': self.email, 'token': self.token, 'subdomain': self.subdomain } self.api = Zenpy(**self.credentials) def clean_response(self, text): return text.replace('\n', ' ').replace(' ', ' ').strip() def url_for_ticket(self, ticket): return 'https://{}.zendesk.com/agent/tickets/{}'.format(self.subdomain, ticket) def api_search(self, query, search_type): return self.api.search(query, type=search_type, sort_by='created_at', sort_order='desc') def create_ticket(self, subject, description): ticket = Ticket(subject=subject, description=description) try: created_ticket_audit = self.api.tickets.create(ticket) return { 'ticket_id': created_ticket_audit.ticket.id, 'ticket_url': self.url_for_ticket(created_ticket_audit.ticket.id), 'subject': self.clean_response(subject), 'description': self.clean_response(description) } except APIException: return {'error': 'Could not create ticket with provided parameters'} except Exception as e: self.logger.error(e) return {'error': 'Could not make API request'} def search_tickets(self, query, search_type='ticket', limit=10): try: query_results = self.api_search(query, search_type) results_clean = map(lambda t: { 'ticket_id': t.id, 'ticket_url': self.url_for_ticket(t.id), 'ticket_status': t.status, 'subject': self.clean_response(t.subject), 'description': self.clean_response(t.description)}, list(query_results)[:limit] ) return {'search_results': results_clean} except APIException: return {'error': 'Could not execute search for query: {}'.format(query)} except Exception as e: self.logger.error(e) return {'error': 'There was an error executing your search'} def update_ticket(self, ticket_id, comment_text, public): try: ticket = self.api.tickets(id=ticket_id) ticket.comment = Comment(body=comment_text, public=public) self.api.tickets.update(ticket) return { 'ticket_id': ticket_id, 'ticket_url': self.url_for_ticket(ticket_id), 'body': self.clean_response(comment_text), 'public': public } except RecordNotFoundException: return {'error': 'Could not find ticket #{}'.format(ticket_id)} except Exception as e: self.logger.error(e) return {'error': 'Could not update ticket'} def update_ticket_status(self, ticket_id, status): valid_statuses = ['new', 'open', 'pending', 'solved', 'closed'] if status in valid_statuses: try: ticket = self.api.tickets(id=ticket_id) ticket.status = status self.api.tickets.update(ticket) return { 'ticket_id': ticket_id, 'ticket_url': self.url_for_ticket(ticket_id), 'status': status } except RecordNotFoundException: return {'error': 'Could not find ticket #{}'.format(ticket_id)} except Exception as e: self.logger.error(e) return {'error': 'Could not update ticket status'} else: return {'error': 'Invalid status given for ticket'}
class ZendeskConnection: def __init__(self): self._creds = { 'email': os.environ.get('ZENDESK_USER', None), 'token': os.environ.get('ZENDESK_TOKEN', None), 'subdomain': "pagarme" } self._zenpy_client = Zenpy(**self._creds) self._sl = SlackConnection.SlackConnection() self._mc = MongoConnection.MongoConnection() def _generate_comment(self, ticket): ticket_comments = "" for comment in self._zenpy_client.tickets.comments(ticket_id=ticket.id): ticket_comments += " " + str(comment) return self._remove_special_characters(ticket_comments) def _remove_special_characters(self, phrase): phrase = phrase.lower() return unidecode(phrase) def _word_in_text(self, word, text): if re.search(r'\b' + word + r'\b', text): return True return False def _tag_ticket(self, ticket): try: TAGS = tags.TAGS new_tags = [] subject = self._remove_special_characters(ticket.subject) ticket_comments = self._generate_comment(ticket) description = self._remove_special_characters(ticket.description) content = subject + " " + ticket_comments + " " + description + " " for tag in TAGS: for word in TAGS[tag]: if (self._word_in_text(word, content)) and (tag not in new_tags): new_tags.append(tag) ticket.tags.extend(new_tags) self._zenpy_client.tickets.update(ticket) except Exception as e: print(e.args) def _get_supporters(self): supporter_list = [] for supporter in self._zenpy_client.search(type='user', group_id=21164867): for email in self._mc.get_active_supporters(): if supporter.email in email['email']: try: supporter_list.append(supporter) except Exception as e: print(e.args) return supporter_list def _get_not_assigned_tickets(self): not_assigned_tickets = list() for tickets in self._zenpy_client.search(type='ticket', group_id=21164867, assignee_id=None, status=['new', 'open', 'pending']): not_assigned_tickets.append(tickets) return not_assigned_tickets def _get_assignees_tickets(self, assignees, statuses): assigned_tickets = list() for ticket in self._zenpy_client.search( type='ticket', group_id=21164867, assignee_id=assignees, status=statuses ): assigned_tickets.append(ticket) return assigned_tickets def _get_pending_interaction_tickets(self, tickets, inactiveHours): pending = list() for ticket in tickets: comments = list(self._zenpy_client.tickets.comments(ticket.id)) latest_comment = comments[len(comments)-1] now = datetime.now() latest_comment_date = datetime.strptime(str(latest_comment.created), "%Y-%m-%d %H:%M:%S+00:00") diff_in_hours = utilities.get_dates_diff_in_hours(now, latest_comment_date) if diff_in_hours >= inactiveHours: pending.append(ticket) return pending def _typing_tickets(self): not_assigned_tickets_with_type = list() fl = open("ticket_log.txt", "r+") read_file_string = fl.read() for ticket in self._get_not_assigned_tickets(): if ticket.type in ['problem', 'incident', 'question', 'task']: not_assigned_tickets_with_type.append(ticket) else: if str(ticket.id) not in read_file_string: self._sl.send_message(ticket) fl.write(" " + str(ticket.id)) fl.close() return not_assigned_tickets_with_type def _get_yesterday_tickets(self): yesterday = datetime.now() - timedelta(days=2) yesterday_tickets = self._zenpy_client.search(type='ticket', created_after=yesterday.date(), group_id=21164867) support_tickets = []; for ticket in yesterday_tickets: support_tickets.append(ticket) return support_tickets def _get_ticket_count_supporter(self): sups = self._get_supporters() ticket_count = [] for sup in sups: tickets = self._zenpy_client.search(type='ticket', group_id=21164867, status=['open', 'pending'], assignee_id=sup.id) count = len(tickets) ticket_count.append({'nome': sup.name, 'count': count, 'id': sup.id}) print('Active supporters: \n' + str(ticket_count)) return ticket_count def tag_yesterday_tickets(self): yesterday_tickets = self._get_yesterday_tickets() tagged_tickets = []; for ticket in yesterday_tickets: self._tag_ticket(ticket) tagged_tickets.append(ticket.id) print('Tagged tickets: ' + ''.join( str(tagged_tickets) ) ) def assign_tickets(self): sups = self._get_ticket_count_supporter() # Send ticket type message through slack tickets = self._typing_tickets() if sups: if len(tickets) > 0: for ticket in tickets: # Get lowest ticket count supporter lowest_ticket_count_sup = None for count in sups: if not lowest_ticket_count_sup: lowest_ticket_count_sup = count elif count['count'] < lowest_ticket_count_sup['count']: lowest_ticket_count_sup = count sup = self._mc.get_supporter_by_zendesk_id(lowest_ticket_count_sup['id']) try: # Assign the ticket ticket.assignee_id = sup['zendesk_id'] slack_id = sup['slack_id'] name = sup['name'] print(slack_id + " | " + name) self._zenpy_client.tickets.update(ticket) # Notify the supporter on slack self._sl.notify_supporter(slack_id, name, ticket) # Increase the ticket count for the agent who got it for agent in sups: if agent['id'] == lowest_ticket_count_sup['id']: agent['count'] += 1 except Exception as e: print(e.args) elif len(tickets) <= 0: print("No tickets to assign.") elif not sups: print("No active agents to assign tickets.") def notify_pending_interaction_tickets(self): supporters = list(self._mc.find_supporters()) supportersIds = map(lambda supporter: supporter["zendesk_id"], supporters) supportersTickets = self._get_assignees_tickets(supportersIds, ["open", "pending", "hold"]) minInactiveHours = 24 pendingInteractionTickets = self._get_pending_interaction_tickets(supportersTickets, minInactiveHours) if len(pendingInteractionTickets) == 0: print("No pending tickets to notify") return for supporter in supporters: supporterTickets = [ticket for ticket in pendingInteractionTickets if str(ticket.assignee_id) == supporter["zendesk_id"]] self._sl.notify_pending_interaction_tickets(supporter, supporterTickets, minInactiveHours)
def delete_all_tickets(): zenpy_client = Zenpy(**settings.ZENDESK) for ticket in zenpy_client.search(status="new"): zenpy_client.tickets.delete(ticket) for ticket in zenpy_client.search(status="open"): zenpy_client.tickets.delete(ticket)
def validate_zendesk_credentials(subdomain: str, user_name: str, api_token: str): from zenpy import Zenpy from zenpy.lib.exception import APIException try: zendesk_client = Zenpy(subdomain=subdomain, email=user_name, token=api_token) list(zendesk_client.search(assignee='test')) except APIException as e: raise ActionFailure(e)
def check_zendesk_ticket(foia): """Check if an open zendesk ticket exists for this FOIA""" client = Zenpy( email=settings.ZENDESK_EMAIL, subdomain=settings.ZENDESK_SUBDOMAIN, token=settings.ZENDESK_TOKEN, ) response = client.search( "{}-{}".format(foia.slug, foia.pk), type="ticket", via="api", subject="FOIAOnline", status="open", ) return len(list(response)) > 0
class ZendeskConnection: def __init__(self): self._creds = { 'email': os.environ.get('ZENDESK_USER', None), 'token': os.environ.get('ZENDESK_TOKEN', None), 'subdomain': "pagarme" } self._zenpy_client = Zenpy(**self._creds) self._sl = SlackConnection.SlackConnection() self._mc = MongoConnection.MongoConnection() def _generate_comment(self, ticket): ticket_comments = "" for comment in self._zenpy_client.tickets.comments( ticket_id=ticket.id): ticket_comments += " " + str(comment) return self._remove_special_characters(ticket_comments) def _remove_special_characters(self, phrase): phrase = phrase.lower() return unidecode(phrase) def _word_in_text(self, word, text): if re.search(r'\b' + word + r'\b', text): return True return False def _tag_ticket(self, ticket): try: TAGS = tags.TAGS new_tags = [] subject = self._remove_special_characters(ticket.subject) ticket_comments = self._generate_comment(ticket) description = self._remove_special_characters(ticket.description) content = subject + " " + ticket_comments + " " + description + " " for tag in TAGS: for word in TAGS[tag]: if (self._word_in_text(word, content)) and (tag not in new_tags): new_tags.append(tag) ticket.tags.extend(new_tags) self._zenpy_client.tickets.update(ticket) except Exception as e: print(e.args) def _get_supporters(self): supporter_list = [] for supporter in self._zenpy_client.search(type='user', group_id=21164867): for email in self._mc.get_active_supporters(): if supporter.email in email['email']: try: supporter_list.append(supporter) except Exception as e: print(e.args) return supporter_list def _get_not_assigned_tickets(self): not_assigned_tickets = list() for tickets in self._zenpy_client.search( type='ticket', group_id=21164867, assignee_id=None, status=['new', 'open', 'pending']): not_assigned_tickets.append(tickets) return not_assigned_tickets def _get_assignees_tickets(self, assignees, statuses): assigned_tickets = list() for ticket in self._zenpy_client.search(type='ticket', group_id=21164867, assignee_id=assignees, status=statuses): assigned_tickets.append(ticket) return assigned_tickets def _get_pending_interaction_tickets(self, tickets, inactiveHours): pending = list() for ticket in tickets: comments = list(self._zenpy_client.tickets.comments(ticket.id)) latest_comment = comments[len(comments) - 1] now = datetime.now() latest_comment_date = datetime.strptime( str(latest_comment.created), "%Y-%m-%d %H:%M:%S+00:00") diff_in_hours = utilities.get_dates_diff_in_hours( now, latest_comment_date) if diff_in_hours >= inactiveHours: pending.append(ticket) return pending def _typing_tickets(self): not_assigned_tickets_with_type = list() fl = open("ticket_log.txt", "r+") read_file_string = fl.read() for ticket in self._get_not_assigned_tickets(): if ticket.type in ['problem', 'incident', 'question', 'task']: not_assigned_tickets_with_type.append(ticket) else: if str(ticket.id) not in read_file_string: self._sl.send_message(ticket) fl.write(" " + str(ticket.id)) fl.close() return not_assigned_tickets_with_type def _get_yesterday_tickets(self): yesterday = datetime.now() - timedelta(days=2) yesterday_tickets = self._zenpy_client.search( type='ticket', created_after=yesterday.date(), group_id=21164867) support_tickets = [] for ticket in yesterday_tickets: support_tickets.append(ticket) return support_tickets def _get_ticket_count_supporter(self): sups = self._get_supporters() ticket_count = [] for sup in sups: tickets = self._zenpy_client.search(type='ticket', group_id=21164867, status=['open', 'pending'], assignee_id=sup.id) count = len(tickets) ticket_count.append({ 'nome': sup.name, 'count': count, 'id': sup.id }) print('Active supporters: \n' + str(ticket_count)) return ticket_count def tag_yesterday_tickets(self): yesterday_tickets = self._get_yesterday_tickets() tagged_tickets = [] for ticket in yesterday_tickets: self._tag_ticket(ticket) tagged_tickets.append(ticket.id) print('Tagged tickets: ' + ''.join(str(tagged_tickets))) def assign_tickets(self): sups = self._get_ticket_count_supporter() # Send ticket type message through slack tickets = self._typing_tickets() if sups: if len(tickets) > 0: for ticket in tickets: # Get lowest ticket count supporter lowest_ticket_count_sup = None for count in sups: if not lowest_ticket_count_sup: lowest_ticket_count_sup = count elif count['count'] < lowest_ticket_count_sup['count']: lowest_ticket_count_sup = count sup = self._mc.get_supporter_by_zendesk_id( lowest_ticket_count_sup['id']) try: # Assign the ticket ticket.assignee_id = sup['zendesk_id'] slack_id = sup['slack_id'] name = sup['name'] print(slack_id + " | " + name) self._zenpy_client.tickets.update(ticket) # Notify the supporter on slack self._sl.notify_supporter(slack_id, name, ticket) # Increase the ticket count for the agent who got it for agent in sups: if agent['id'] == lowest_ticket_count_sup['id']: agent['count'] += 1 except Exception as e: print(e.args) elif len(tickets) <= 0: print("No tickets to assign.") elif not sups: print("No active agents to assign tickets.") def notify_pending_interaction_tickets(self): supporters = list(self._mc.find_supporters()) supportersIds = map(lambda supporter: supporter["zendesk_id"], supporters) supportersTickets = self._get_assignees_tickets( supportersIds, ["open", "pending", "hold"]) minInactiveHours = 24 pendingInteractionTickets = self._get_pending_interaction_tickets( supportersTickets, minInactiveHours) if len(pendingInteractionTickets) == 0: print("No pending tickets to notify") return for supporter in supporters: supporterTickets = [ ticket for ticket in pendingInteractionTickets if str(ticket.assignee_id) == supporter["zendesk_id"] ] self._sl.notify_pending_interaction_tickets( supporter, supporterTickets, minInactiveHours)
class ZendeskConnection: def __init__(self): self._creds = { 'email': os.environ.get('ZENDESK_USER', None), 'token': os.environ.get('ZENDESK_TOKEN', None), 'subdomain': "pagarme" } self._zenpy_client = Zenpy(**self._creds) self._sl = SlackConnection.SlackConnection() self._mc = MongoConnection.MongoConnection() def _generate_comment(self, ticket): ticket_comments = "" for comment in self._zenpy_client.tickets.comments( ticket_id=ticket.id): ticket_comments += " " + str(comment) return self._remove_special_characters(ticket_comments) def _remove_special_characters(self, phrase): phrase = phrase.lower() return unidecode(phrase) def _tag_ticket(self, ticket): try: TAGS = tags.TAGS new_tags = [] subject = self._remove_special_characters(ticket.subject) ticket_comments = self._generate_comment(ticket) description = self._remove_special_characters(ticket.description) for tag in TAGS: for word in TAGS[tag]: if (((word in subject) or (word in description) or (word in ticket_comments)) and (tag not in new_tags)): new_tags.append(tag) ticket.tags.extend(new_tags) self._zenpy_client.tickets.update(ticket) except Exception as e: print(e.args) def _get_supporters(self): supporter_list = [] for supporter in self._zenpy_client.search(type='user', group_id=21164867): for email in self._mc.get_active_supporters(): if supporter.email in email['email']: try: supporter_list.append(supporter) except Exception as e: print(e.args) return supporter_list def _get_not_assigned_tickets(self): not_assigned_tickets = list() for tickets in self._zenpy_client.search( type='ticket', group_id=21164867, assignee_id=None, status=['new', 'open', 'pending']): not_assigned_tickets.append(tickets) return not_assigned_tickets def _typing_tickets(self): not_assigned_tickets_with_type = list() fl = open("ticket_log.txt", "r+") read_file_string = fl.read() for ticket in self._get_not_assigned_tickets(): if ticket.type in ['problem', 'incident', 'question', 'task']: not_assigned_tickets_with_type.append(ticket) else: if str(ticket.id) not in read_file_string: self._sl.send_message(ticket) fl.write(" " + str(ticket.id)) fl.close() return not_assigned_tickets_with_type def _get_yesterday_tickets(self): yesterday = datetime.now() - timedelta(days=2) yesterday_tickets = self._zenpy_client.search( type='ticket', created_after=yesterday.date(), group_id=21164867) support_tickets = [] for ticket in yesterday_tickets: support_tickets.append(ticket) return support_tickets def _get_ticket_count_supporter(self): sups = self._get_supporters() ticket_count = [] for sup in sups: tickets = self._zenpy_client.search(type='ticket', group_id=21164867, status=['open', 'pending'], assignee_id=sup.id) count = len(tickets) ticket_count.append({ 'nome': sup.name, 'count': count, 'id': sup.id }) print('Active supporters: \n' + str(ticket_count)) return ticket_count def tag_yesterday_tickets(self): yesterday_tickets = self._get_yesterday_tickets() tagged_tickets = [] for ticket in yesterday_tickets: self._tag_ticket(ticket) tagged_tickets.append(ticket.id) print('Tagged tickets: ' + ''.join(str(tagged_tickets))) def assign_tickets(self): sups = self._get_ticket_count_supporter() # Send ticket type message through slack tickets = self._typing_tickets() if sups: if len(tickets) > 0: for ticket in tickets: # Get lowest ticket count supporter lowest_ticket_count_sup = None for count in sups: if not lowest_ticket_count_sup: lowest_ticket_count_sup = count elif count['count'] < lowest_ticket_count_sup['count']: lowest_ticket_count_sup = count sup = self._mc.get_supporters_by_zendesk_id( lowest_ticket_count_sup['id']) try: # Assign the ticket ticket.assignee_id = sup['zendesk_id'] slack_id = sup['slack_id'] name = sup['name'] print(slack_id + " | " + name) self._zenpy_client.tickets.update(ticket) # Notify the supporter on slack self._sl.notify_supporter(slack_id, name, ticket) # Increase the ticket count for the agent who got it for agent in sups: if agent['id'] == lowest_ticket_count_sup['id']: agent['count'] += 1 except Exception as e: print(e.args) elif len(tickets) <= 0: print("No tickets to assign.") elif not sups: print("No active agents to assign tickets.")
from zenpy import Zenpy import datetime creds = { 'email' : '*****@*****.**', 'token' : 'zgXYhMmdxGTT0gMPVOaFfRf5xUNue4UPNsE4MaGs', 'subdomain': 'https://envusa.zendesk.com'} zenpy_client = Zenpy(**creds) yesterday = datetime.datetime.now() - datetime.timedelta(days=1) today = datetime.datetime.now() for ticket in zenpy_client.search("zenpy", created_between=[yesterday, today], type='ticket', minus='negated'): print(ticket)
args = parser.parse_args() try: with open('./creds.json', 'r') as f: creds = json.load(f) except FileNotFoundError: raise FileNotFoundError("No creds.json found in current directory. Please make sure one exists before running this" " script.") ticketNums, rmDirs = [], [] count = 0 caseDir = args.directory zenpy_client = Zenpy(**creds) print("Getting list of open Zendesk tickets...") for ticket in zenpy_client.search("type:ticket status:open status:new status:hold status:pending assignee:" + creds.get("email")): ticketNums.append(str(ticket.id)) # next(os.walk())[1] gets a list of all top-level directories in the path specified, and will avoid any literal files # and/or subdirectories for directory in next(os.walk(caseDir))[1]: if directory not in ticketNums: rmDirs.append(directory) rmDirs.sort() if not args.force: for item in rmDirs: valid = False while not valid:
email = input('Email (default "{0}"): '.format(default_email)) or default_email token = input('Token: ') subdomain = input('Subdomain (default "{0}"): '.format( default_subdomain)) or default_subdomain # Zendesk client client = Zenpy(**{ 'email': email, 'token': token, 'subdomain': subdomain, }) # Query for tickets start_date = datetime(year=2017, month=12, day=3, tzinfo=timezone.utc) end_date = datetime(year=2017, month=12, day=5, tzinfo=timezone.utc) tickets = client.search(type='ticket', created_between=(start_date, end_date)) # Write tickets as tsv with open('result.tsv', 'w') as outfile: writer = csv.writer(outfile, delimiter='\t') # tab delimiter # Write headers writer.writerow([ 'Ticket ID', 'Date', 'Username', 'Email', 'Brand', 'Tags', 'Style', 'Product',
def main(): # Fetch tickets from ZD (as a list of dictionaries) # load from disk if possible; if not call Zendesk API and save to disk logger.info("Fetching ZD tickets") zd_tickets = [] zenpy_client = Zenpy(**zd_creds) begin_datetime = datetime.datetime(2019, 1, 1, 0, 0, 0, 0) end_datetime = datetime.datetime(2019, 1, 31, 0, 0, 0, 0) zd_tickets_filepath = "zd_tickets.json" if os.path.exists(zd_tickets_filepath): with open(zd_tickets_filepath) as f: zd_tickets = json.load(f) else: for ticket in zenpy_client.search( "Open Access enquiry has been received", created_between=[begin_datetime, end_datetime], type='ticket'): zd_tickets.append(ticket.to_dict()) with open(zd_tickets_filepath, "w") as f: json.dump(zd_tickets, f) # Keep only tickets with zd_field_DspaceID not null # extract useful info from those tickets logger.info("Keeping only tickets with zd_field_DspaceID not null") _ = [] for t in zd_tickets: include_ticket = False for c in t['custom_fields']: if (c['id'] == ZdFields.internal_item_id_apollo) and c['value']: t[DSPACE_ID_TAG] = c['value'] include_ticket = True for k, v in FIELDS_OF_INTEREST.items(): if c['id'] == k: t[v] = c['value'] if include_ticket: t['original_files'] = parse_zd_ticket_description(t['description']) _.append(t) zd_tickets = _ # Obtain the data and files we need from Apollo # Load from disk if possible; otherwise use API test_cases_filepath = "test_cases.json" if os.path.exists(test_cases_filepath): logger.info( "Loading test cases from disk (delete {} " "to collect from server instead)".format(test_cases_filepath)) with open(test_cases_filepath) as f: test_cases = json.load(f) else: # Create a DSpace API client instance and login logger.info("Logging in to DSpace API") client = Dspace5Client() client.login() # Keep only tickets that: # 1-) have been archived in DSpace staging; # (i.e. we know what file version was made available/approved) logger.info("Collecting test cases") test_cases = [] for t in zd_tickets: item = client.get_item(t[DSPACE_ID_TAG]) if item['archived'] == 'true': bitstreams = client.get_item_bitstreams(t[DSPACE_ID_TAG]) tc = TestCase() tc.zd_ticket = t tc.dspace_id = t[DSPACE_ID_TAG] tc.dspace_item = item tc.dspace_bitstreams = bitstreams test_cases.append(tc) # TODO: ged rid of this break when ready to test many cases if len(test_cases) > 10: break with open(test_cases_filepath, "w") as f: json.dump(test_cases, f) # Changing wd to download folder os.chdir(downloads_folder) logger.info("Working on test cases") with open(OUTPUT_CSV, "w") as f: header = ["bitstream", "Apollo version", "outcome", "version/details"] csv_writer = csv.DictWriter(f, fieldnames=header, extrasaction='ignore') csv_writer.writeheader() for tc in test_cases[:19]: for bs in tc.dspace_bitstreams: if (bs['bundleName'] == 'ORIGINAL') and (bs['description'].lower() not in ['supporting information']): if bs['name'] not in os.listdir(downloads_folder): client.download_bitstream(bs['id'], downloads_folder) vd = VersionDetector( os.path.join(downloads_folder, bs['name']), dec_ms_title=tc.dspace_item['name'], dec_version=bs['description'], dec_authors=extract_list_of_authors_from_ds_metadata( client.get_item_metadata(tc.dspace_id)), # **{'doi': 'foo'} ) # if vd.check_extension() == 'docx': # restrict tests to docx for now result = vd.detect() logger.info(result) row = { "bitstream": bs['name'], "Apollo version": bs['description'], "outcome": result[0], "version/details": result[1] } csv_writer.writerow(row)
class ZengoService(object): """Encapsulate behaviour allowing easy customisation.""" def __init__(self, *args, **kwargs): creds = { "email": settings.ZENDESK_EMAIL, "token": settings.ZENDESK_TOKEN, "subdomain": settings.ZENDESK_SUBDOMAIN, } self.client = Zenpy(**creds) # extraction of data from local users for injection into Zendesk def get_local_user_name(self, local_user): return local_user.first_name def get_local_user_external_id(self, local_user): return local_user.id def get_local_user_profile_image(self, local_user): return None def get_local_user_for_external_id(self, external_id): return get_user_model().objects.filter(id=external_id).first() def get_remote_zd_user_for_local_user(self, local_user): """ Attempt to resolve the provided user to an extant Zendesk User. Returns a Zendesk API User instance, and a boolean that indicates how definite the resolution is, based on how they've been found. """ result = self.client.search( type="user", external_id=self.get_local_user_external_id(local_user) ) if result.count: # strong match by external_id return result.next(), True # else check by associated emails, if allauth is installed and being used if "allauth.account" in settings.INSTALLED_APPS: emails = local_user.emailaddress_set.all() for e in emails: result = self.client.search(type="user", email=e.email) if result.count: # match strength based on email verification state return result.next(), e.verified # check for a weak match match using the email field on user instance if local_user.email: result = self.client.search(type="user", email=local_user.email) if result.count: return result.next(), False # no match at all, buh-bow return None, False def create_remote_zd_user_for_local_user(self, local_user): """Create a remote zendesk user based on the given local user's details.""" try: remote_zd_user = self.client.users.create( RemoteZendeskUser( name=self.get_local_user_name(local_user), external_id=self.get_local_user_external_id(local_user), email=local_user.email, remote_photo_url=self.get_local_user_profile_image(local_user), ) ) except APIException as a: # if this is a duplicate error try one last time to get the user print(a.response.json()) details = a.response.json()["details"] if any([d[0]["error"] == "DuplicateValue" for d in details.values()]): ( remote_zd_user, is_definite_match, ) = self.get_remote_zd_user_for_local_user(local_user) else: raise return remote_zd_user def get_or_create_remote_zd_user_for_local_user(self, local_user): user, is_definite_match = self.get_remote_zd_user_for_local_user(local_user) if user: return user, is_definite_match # we create a remote user in Zendesk for this local user return self.create_remote_zd_user_for_local_user(local_user), True def get_special_zendesk_user(self): """ Return a ZendeskUser instance representing the special Zendesk user that automations can add use to add comments. """ instance, created = models.ZendeskUser.objects.get_or_create( zendesk_id=-1, defaults=dict( name="Zendesk", active=True, role=models.ZendeskUser.roles.admin, created_at=timezone.now(), ), ) return instance def update_remote_zd_user_for_local_user(self, local_user, remote_zd_user): """ Compare the User and ZendeskUser instances and determine whether we need to update the data in Zendesk. This method is suitable to be called when a local user changes their email address or other user data. """ changed = False email_changed = False if self.get_local_user_name(local_user) != remote_zd_user.name: remote_zd_user.name = self.get_local_user_name(local_user) changed = True if self.get_local_user_external_id(local_user) != remote_zd_user.external_id: remote_zd_user.external_id = self.get_local_user_external_id(local_user) changed = True if local_user.email and local_user.email != remote_zd_user.email: remote_zd_user.email = local_user.email changed = True email_changed = True if changed: remote_zd_user = self.client.users.update(remote_zd_user) if email_changed: # then in addition to the above, we have to mark the newly # created identity as verified and promote it to be primary results = self.client.users.identities(id=remote_zd_user.id) for identity in results: if identity.value == local_user.email: self.client.users.identities.make_primary( user=remote_zd_user, identity=identity ) break def update_or_create_remote_zd_user(self, local_user): remote_zd_user, is_definite_match = self.get_remote_zd_user_for_local_user( local_user ) if remote_zd_user and is_definite_match: # check if we need to do any updates self.update_remote_zd_user_for_local_user(local_user, remote_zd_user) else: remote_zd_user = self.create_remote_zd_user_for_local_user(local_user) return remote_zd_user def sync_user(self, remote_zd_user): """ Given a RemoteZendeskUser instance, persist it as a local ZendeskUser instance. """ instance, created = models.ZendeskUser.objects.update_or_create( zendesk_id=remote_zd_user.id, defaults=dict( # attempt to resolve the local user if possible user=self.get_local_user_for_external_id(remote_zd_user.external_id), alias=remote_zd_user.alias, email=remote_zd_user.email, created_at=remote_zd_user.created_at, name=remote_zd_user.name, active=remote_zd_user.active, role=remote_zd_user.role, # store their latest photo JSON data photos_json=json.dumps(remote_zd_user.photo), ), ) return instance def sync_ticket_id(self, ticket_id): return self.sync_ticket(self.client.tickets(id=ticket_id)) def sync_ticket(self, remote_zd_ticket): """ Create or update local representations of a Zendesk ticket, its comments and all associated Zendesk users. This uses `update_or_create` to avoid integrity errors, demanding that comments and users be sync'd in a consistent order to avoid deadlock. Todo: only pull comments beyond those we've already got in the database """ kwargs = dict(include_inline_images=True) remote_comments = [ c for c in self.client.tickets.comments(remote_zd_ticket.id, **kwargs) ] remote_comments.sort(key=lambda c: (c.created_at, c.id)) # establish a distinct, ordered list of Zendesk users users = set( [remote_zd_ticket.requester] + [c.author for c in remote_comments if c.author_id != -1] # noqa ) users = list(users) users.sort(key=lambda u: u.id) # sync the users and establish a mapping to local records user_map = {u: self.sync_user(u) for u in users} defaults = dict( requester=user_map[remote_zd_ticket.requester], subject=remote_zd_ticket.subject, url=remote_zd_ticket.url, status=models.Ticket.states.by_id.get(remote_zd_ticket.status.lower()), custom_fields=json.dumps(remote_zd_ticket.custom_fields), tags=json.dumps(remote_zd_ticket.tags), created_at=remote_zd_ticket.created_at, updated_at=remote_zd_ticket.updated_at, ) # In some API responses we don't get a priority, but it could be an existing ticket with # priority already initialised so we don't want to overwrite the priority to the Ticket # object. if remote_zd_ticket.priority is not None: defaults["priority"] = models.Ticket.priorities.by_id.get( remote_zd_ticket.priority.lower() ) # update or create the ticket local_ticket, created = models.Ticket.objects.update_or_create( zendesk_id=remote_zd_ticket.id, defaults=defaults, ) # and now update or create the comments - baring in mind some might be type `VoiceComment` # https://developer.zendesk.com/rest_api/docs/support/ticket_audits#voice-comment-event for remote_comment in remote_comments: # if we know Zendesk created this comment as part of an automation or # merge, link it to the Zendesk user (skipping any zenpy/network hits) if remote_comment.author_id == -1: author = self.get_special_zendesk_user() else: author = user_map[remote_comment.author] local_comment, _created = models.Comment.objects.update_or_create( zendesk_id=remote_comment.id, ticket=local_ticket, defaults=dict( author=author, body=remote_comment.body, html_body=remote_comment.html_body, # VoiceComments have no `plain_body` content plain_body=getattr(remote_comment, "plain_body", None), public=remote_comment.public, created_at=remote_comment.created_at, ), ) for attachment in remote_comment.attachments: local_attachment, _created = models.Attachment.objects.update_or_create( zendesk_id=attachment.id, comment=local_comment, defaults=dict( file_name=attachment.file_name, content_url=attachment.content_url, content_type=attachment.content_type, size=attachment.size, width=attachment.width, height=attachment.height, inline=attachment.inline, ), ) for photo in attachment.thumbnails: local_photo, _created = models.Photo.objects.update_or_create( zendesk_id=photo.id, attachment=local_attachment, defaults=dict( file_name=photo.file_name, content_url=photo.content_url, content_type=photo.content_type, size=photo.size, width=photo.width, height=photo.height, ), ) return local_ticket, created
# RGB LED pin mapping. red = {{8}} green = {{10}} blue = {{12}} # Set RGB LED pins as output GPIO.setup(red, GPIO.OUT) GPIO.setup(green, GPIO.OUT) GPIO.setup(blue, GPIO.OUT) turnoff() #init rgb as off for start last_hour = datetime.datetime.now() - datetime.timedelta(hours=1) current_hour = datetime.datetime.now() for ticket in zenpy_client.search("zenpy", created_between=[last_hour, current_hour], type='ticket', minus='negated'): tp = ticket.priority if tp == 'Low': low() # turn rgb blue time.sleep(2000) # wait for 2 seconds turnoff() # turn off the RGB if tp == 'Normal': normal() # turn rgb green time.sleep(2000) # wait for 2 seconds turnoff() # turn off the RGB if tp == 'High': high() # turn rgb yellow time.sleep(2000) # wait for 2 seconds turnoff() # turn off the RGB
def main(): parser = argparse.ArgumentParser( description="""Zendesk APIはsubdomain/email/token の3つを認証情報として要求します。 環境変数ZENDESK_SUBDOMAIN, ZENDESK_EMAIL, ZENDESK_TOKENで定義するか、 コマンドライン引数で直接指定してください。 両方で指定した場合はコマンドライン引数を優先します。 """) parser.add_argument('--keyword', type=str, dest='keyword', required=True, help='keyword will serach from subject') parser.add_argument('--subdomain', type=str, dest='subdomain', help='zendesk subdomain') parser.add_argument('--email', type=str, dest='email', help='zendesk email') parser.add_argument('--token', type=str, dest='token', help='zendesk token') parser.add_argument('--output', type=str, dest='output', default='output.csv', help='output file. default=output.csv') args = parser.parse_args() credential = { 'subdomain': os.environ.get('ZENDESK_SUBDOMAIN', ''), 'email': os.environ.get('ZENDESK_EMAIL', ''), 'token': os.environ.get('ZENDESK_TOKEN', '') } if getattr(args, 'subdomain', False): credential['subdomain'] = args.subdomain if getattr(args, 'email', False): credential['email'] = args.email if getattr(args, 'token', False): credential['token'] = args.token zendesk_client = Zenpy(**credential) resp = list(zendesk_client.search(args.keyword)) rows = [] rows.append([ 'type', 'id', 'created_at', 'subject', 'organization_id', 'organization_name', 'requester_id', 'requester_name', 'assignee_id', 'assignee_name', 'status', 'url' ]) for r in tqdm(resp): try: rows.append([ r.type, r.id, r.created_at, r.subject, r.organization_id, r.organization.name if r.organization is not None else '', r.requester.id, r.requester.name if r.requester is not None else '', r.assignee_id, r.assignee.name if r.assignee is not None else '', r.status, r.url ]) except Exception as e: logger.warning(f'skip ticketId={r.id}, url={r.url}') logger.warning(f'exception: {e}') continue with open(args.output, 'w') as fp: wr = csv.writer(fp) wr.writerows(rows)
from zenpy import Zenpy from zenpy.lib.api_objects import Ticket from win10toast import ToastNotifier import time creds = { 'email' : username, 'password' : password, 'subdomain' : the first part of the url } toaster = ToastNotifier() newTicket = [] while 1 != 2: zenpyClient = Zenpy(**creds) for ticket in zenpyClient.search(type='ticket', status='new'): if str(ticket) in newTicket: continue else: newTicket.append(str(ticket)) text = str(ticket.subject) toaster.show_toast("New Ticket", text, duration = 10) time.sleep(30)
class ZendeskAction(Action): def __init__(self, config): super(ZendeskAction, self).__init__(config=config) self.email = self.config['email'] self.token = self.config['api_token'] self.subdomain = self.config['subdomain'] self.credentials = { 'email': self.email, 'token': self.token, 'subdomain': self.subdomain } self.api = Zenpy(**self.credentials) def clean_response(self, text): return text.replace('\n', ' ').replace(' ', ' ').strip() def url_for_ticket(self, ticket): return 'https://{}.zendesk.com/agent/tickets/{}'.format( self.subdomain, ticket) def api_search(self, query, search_type): return self.api.search(query, type=search_type, sort_by='created_at', sort_order='desc') def create_ticket(self, subject, description): ticket = Ticket(subject=subject, description=description) try: created_ticket_audit = self.api.tickets.create(ticket) return { 'ticket_id': created_ticket_audit.ticket.id, 'ticket_url': self.url_for_ticket(created_ticket_audit.ticket.id), 'subject': self.clean_response(subject), 'description': self.clean_response(description) } except APIException: return { 'error': 'Could not create ticket with provided parameters' } except Exception as e: self.logger.error(e) return {'error': 'Could not make API request'} def search_tickets(self, query, search_type='ticket', limit=10): try: query_results = self.api_search(query, search_type) results_clean = map( lambda t: { 'ticket_id': t.id, 'ticket_url': self.url_for_ticket(t.id), 'ticket_status': t.status, 'subject': self.clean_response(t.subject), 'description': self.clean_response(t.description) }, list(query_results)[:limit]) return {'search_results': results_clean} except APIException: return { 'error': 'Could not execute search for query: {}'.format(query) } except Exception as e: self.logger.error(e) return {'error': 'There was an error executing your search'} def update_ticket(self, ticket_id, comment_text, public): try: ticket = self.api.tickets(id=ticket_id) ticket.comment = Comment(body=comment_text, public=public) self.api.tickets.update(ticket) return { 'ticket_id': ticket_id, 'ticket_url': self.url_for_ticket(ticket_id), 'body': self.clean_response(comment_text), 'public': public } except RecordNotFoundException: return {'error': 'Could not find ticket #{}'.format(ticket_id)} except Exception as e: self.logger.error(e) return {'error': 'Could not update ticket'} def update_ticket_status(self, ticket_id, status): valid_statuses = ['new', 'open', 'pending', 'solved', 'closed'] if status in valid_statuses: try: ticket = self.api.tickets(id=ticket_id) ticket.status = status self.api.tickets.update(ticket) return { 'ticket_id': ticket_id, 'ticket_url': self.url_for_ticket(ticket_id), 'status': status } except RecordNotFoundException: return {'error': 'Could not find ticket #{}'.format(ticket_id)} except Exception as e: self.logger.error(e) return {'error': 'Could not update ticket status'} else: return {'error': 'Invalid status given for ticket'}
class DataQuery(object): filter_to_zenpy = { 'new': ['status', 'new'], 'open': ['status', 'open'], 'pending': ['status', 'pending'], 'closed': ['status', 'closed'], 'solved': ['status', 'solved'], 'hold': ['status', 'hold'], #'on-hold': ['status', 'hold'], #'low': ['priority', 'low'], 'low priority': ['priority', 'low'], #'medium': ['priority', 'medium'], 'medium priority': ['priority', 'medium'], #'priority': ['priority', 'high'], # TODO is this wise? ... NO! #'high': ['priority', 'high'], 'high priority': ['priority', 'high'], 'urgent': ['priority', 'urgent'], 'critical': ['priority', 'urgent'], } filter_to_postprocess = { #one-touch 'oldest': ['age', 'max'], 'newest': ['age', 'min'], 'youngest': ['age', 'min'], 'most recent': ['age', 'min'] #Newest Youngest, Most recent } event_to_zenpy = { 'deleted': 'deleted_between', 'reopened': 'reopened_between', 'created': 'created_between', 'added': 'created_between', 'opened': 'created_between', 'solved': 'solved_between', 'closed': 'closed_between', 'updated': 'updated_between', 'due': 'due_date_between', } from_to_zenpy = { 'tickets': ['type', 'ticket'], 'issues': ['type', 'ticket'], 'agents': ['type', 'agent'], 'organizations': ['type', 'organization'], 'users': ['type', 'user'], 'incidents': ['ticket_type', 'incident'], 'problems': ['ticket_type', 'problem'], 'tasks': ['ticket_type', 'task'], 'questions': ['ticket_type', 'question'] } entity_to_zenpy = { } def __init__(self, parameters): self.zenpy_client = False self.filters = [] self.query_from = False self.event = False self.period = False self.entity = False # Go through both filter and betafilter and add each word from them for param in ['filter', 'betafilter']: for allowed_filter in self.filter_to_zenpy: if param in parameters: if (not parameters[param] is None) and (allowed_filter in parameters[param]) and (allowed_filter not in self.filters): self.filters.append(allowed_filter) if 'event' in parameters: self.event = parameters['event'] if 'from' in parameters: self.query_from = parameters['from'] elif 'query_from' in parameters: self.query_from = parameters['query_from'] if 'period' in parameters: self.period = parameters['period'] if self.event == False or self.event is None: self.event = 'created' if 'entity' in parameters: self.entity = parameters['entity'] # TODO This converts input params to lowercase, unsure if it's a good idea to be so defensive... for d in self.__dict__: if isinstance(self.__dict__[d], basestring): self.__dict__[d] = self.__dict__[d].lower() def validate(self): ret = [] has_filter_type = {} for param in self.filters: if not param in self.filter_to_zenpy and not param in self.filter_to_postprocess: ret.append({'parameter': 'filter', 'value': param, 'reason': 'unknown'}) # Check if filter with same type alrady exists => contradiction! if param in self.filter_to_zenpy: filter_type = self.filter_to_zenpy[param][0] if filter_type in has_filter_type: ret.append({'parameter': 'filter', 'value': param, 'reason': 'contradiction', 'contradiction_value': has_filter_type[filter_type]}) else: has_filter_type[ filter_type ] = param if self.event and not self.event in self.event_to_zenpy: ret.append({'parameter': 'event', 'value': self.event, 'reason': 'unknown'}) if self.query_from and not self.query_from in self.from_to_zenpy: ret.append({'parameter': 'from', 'value': self.event, 'reason': 'unknown'}) if self.period != False and self.period != None and parse_period(self.period) is None: ret.append({'parameter': 'period', 'value': self.period, 'reason': 'unknown'}) if self.entity and not self.entity in self.entity_to_zenpy: ret.append({'parameter': 'entity', 'value': self.entity, 'reason': 'unknown'}) if len(self.filters) == 0 and self.event == False and self.entity == False: ret.append({'parameter': 'filter', 'value': None, 'reason': 'empty'}) # Make request invalid if no status filter given without an event. Due to performance reasons if self.event == False and (not 'status' in has_filter_type): ret.append({'parameter': 'filter', 'value': None, 'reason': 'performance'}) # Make request invalid if filter on closed/solved without an event. Due to performance reasons elif self.event == False and (has_filter_type['status'] == 'closed' or has_filter_type['status'] == 'solved'): ret.append({'parameter': 'filter', 'value': has_filter_type['status'], 'reason': 'performance'}) return ret # Simple json serialization def toJson(self): jsonobj = self.__dict__ jsonobj['zenpy_client'] = None jsonobj['filters'] = self.filters return json.dumps(jsonobj) def toDict(self): obj = self.__dict__ obj['zenpy_client'] = None obj['filters'] = self.filters return obj # Simple json deserialization @staticmethod def fromJson(jsonIn): params = json.loads(jsonIn) if 'filters' in params: params['filter'] = " ".join(params['filters']) return DataQuery(params) def execute(self): # select data via API from # right endpoint via from # right time period via period # filter on entity, filter and betafilter as required # Apply entry = prepare_row(from, entry) for each result entry creds = { 'email' : os.environ['DATABOT_ZD_EMAIL'], 'token' : os.environ['DATABOT_ZD_TOKEN'], 'subdomain': os.environ['DATABOT_ZD_SUBDOMAIN'] } zenpy_query = {} query_from = self.from_to_zenpy[ self.query_from ] zenpy_query[ query_from[0] ] = query_from[1] ## Apply filters for filter in self.filters: if filter in self.filter_to_zenpy: f = self.filter_to_zenpy[ filter ] zenpy_query[ f[0] ] = f[1] ## Create period based on events if self.period: period_date = parse_period(self.period) now = datetime.datetime.now() #event = self.event if self.event != False else 'created' # Done when creating query instead if self.event in self.event_to_zenpy: event = self.event_to_zenpy[ self.event ] else: evemt = self.event+"_between" if period_date < now: zenpy_query[event] = [period_date, now] else: zenpy_query[event] = [now, period_date] # Execute the actual search self.zenpy_client = Zenpy(**creds) logger.info("Query sent to Zendesk: {}".format(zenpy_query)) zenpy_search = self.zenpy_client.search(**zenpy_query) result = [] print zenpy_search for ticket in zenpy_search: result.append(self.prepare_row(self.query_from, ticket)) # Apply post process filters, gettin oldest, newest entry etc. for filter in self.filters: if filter in self.filter_to_postprocess: f = self.filter_to_postprocess[ filter ] metric = DataMetric({ 'value': f[0], 'metric': f[1] }, self.query_from) res = metric.calc_on_resultSet(result) if res.entry: result = [res.entry] elif isinstance(res.result, list): result = res.result return result def prepare_row(self, frm, entry): ret = {} #if frm == 'tickets': #print entry if isinstance(entry, Ticket): age = datetime.datetime.now() - datetime.datetime.strptime(entry.created_at, '%Y-%m-%dT%H:%M:%SZ') ret['age'] = age.total_seconds() ret['ticket_id'] = entry.id #print entry.via.source #ret['from_email'] = entry.via.source.from_.address if not entry.via.source.from_ is None else None ret['updated'] = entry.updated_at ret['created'] = entry.created_at ret['subject'] = entry.subject ret['priority'] = entry.priority ret['type'] = entry.type ret['status'] = entry.status ret['tags'] = entry.tags #metrics = self.zenpy_client.tickets.metrics(entry.id) #ret['replies'] = metrics.replies #if not metrics.assigned_at is None: # assignment_time = datetime.datetime.now() - datetime.datetime.strptime(metrics.assigned_at, '%Y-%m-%dT%H:%M:%SZ') # ret['assignment_time'] = assignment_time.total_seconds() #satisfaction_ratings = { #'offered': False, 'unoffered': False, # 'bad': 0, 'good': 1} #if entry.satisfaction_rating.score in satisfaction_ratings: # ret['satisfaction_rating'] = satisfaction_ratings[ entry.satisfaction_rating.score ] return ret