コード例 #1
3
ファイル: zendesk.py プロジェクト: Pulsant/st2contrib
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'}
コード例 #2
0
def close_ticket(prescription):
    if not isinstance(prescription, MedicalPrescription):
        return False

    mticket = ZendeskTicket.objects.filter(prescription=prescription).first()
    zenpy_client = Zenpy(**settings.ZENDESK)

    if not mticket:
        return False

    ticket = zenpy_client.tickets(id=mticket.external_id)

    ticket.status = 'solved'

    if prescription.status in domain_models.MedicalPrescription.STATUS_CHOICES:
        new_cf = []
        for cf in ticket.custom_fields:
            if cf.get('id', None) == 114103846891:
                new_cf.append({
                    'id': 114103846891,
                    'value': prescription.status.lower()
                })
            new_cf.append(cf)

        ticket.custom_fields = new_cf

    ticket_audit = zenpy_client.tickets.update(ticket)

    if ticket_audit:
        return True
    return False
コード例 #3
0
def index():
  try: # We use try method to keep running if there is no API available.
    # Default
    zenpy_client = Zenpy(**creds)
    ticket_generator = zenpy_client.tickets()
    return render_template('index.html', tickets=ticket_generator)
  except:
    print("Sorry, API isn't currently available")
コード例 #4
0
def get_tickets(client: Zenpy) -> List:
    """
    Gets all tickets from the Zendesk API for the workspace.
    N.B.: will be inefficient at large size, should paginate requests
    in future.
    :param client: the Zenpy client connection object
    :return: a list of all tickets for the client workspace
    """
    return list(client.tickets())
コード例 #5
0
def get_description(ticket_id):
    global zenpy_client
    try:
        zenpy_client = Zenpy(**credentials)
        ticket = zenpy_client.tickets(id=ticket_id)
        return ticket.description
    except Exception as e:
        logger.exception("get_description {}".format(e))
        raise
コード例 #6
0
def get_unclosed_tickets(email: str, token: str,
                         subdomain: str) -> Iterable[Ticket]:
    zenpy = Zenpy(subdomain, email, token)
    ticket: ZendeskTicket
    for ticket in zenpy.tickets():
        requester: ZendeskUser = ticket.requester
        waiting_on = None
        staled_at = None
        yield Ticket(ticket.id, ticket.subject, requester.email,
                     requester.email, ticket.status, waiting_on, staled_at,
                     ticket.created_at)
コード例 #7
0
def update_ticket(ticket_id, message):
    global zenpy_client
    comment = "```\n{}\n```".format(message)
    try:
        zenpy_client = Zenpy(**credentials)
        ticket = zenpy_client.tickets(id=ticket_id)
        ticket.comment = Comment(body=comment, public=False)
        zenpy_client.tickets.update(ticket)
    except Exception as e:
        logger.exception("update_ticket {}".format(e))
        raise
コード例 #8
0
def close_adf_ticket(ticket):
    if not isinstance(ticket, ZendeskASFTicket):
        return False

    zenpy_client = Zenpy(**settings.ZENDESK)

    ticket = zenpy_client.tickets(id=ticket.external_id)

    ticket.status = 'solved'

    ticket_audit = zenpy_client.tickets.update(ticket)

    if ticket_audit:
        return True
    return False
コード例 #9
0
def update_zendesk_ticket(ticket_id, comment=None, status=None):
    client = Zenpy(
        subdomain=settings.ZENDESK_SUBDOMAIN,
        email=settings.ZENDESK_EMAIL,
        token=settings.ZENDESK_TOKEN,
    )

    ticket = client.tickets(id=ticket_id)

    if comment:
        ticket.comment = Comment(body=comment, public=False)

    if status:
        ticket.status = status

    client.tickets.update(ticket)

    return ticket
コード例 #10
0
def display_ticket(number: str, client: Zenpy) -> None:
    """
    Displays detailed view of a single ticket
    :param number: the ID number of the ticket to display
    :param client: the Zenpy client connection object
    """
    try:
        ticket = client.tickets(id=number)
    except ZenpyException:
        print("Error accessing ticket with that ID. Are you sure it exists?")
        return
    print(
        f"{'*' * 80}\n"
        f"{pad('Ticket ID: ', 15)}{pad(str(ticket.id), 65)}\n"
        f"{pad('Subject: ', 15)}{pad(ticket.subject, 65)}\n"
        f"{pad('Description: ', 15)}{pad(sanitise_description(ticket.description), 65)}\n"
        f"{pad('Created at: ', 15)}{pad(ticket.created_at, 65)}\n"
        f"{pad('Submitted by: ', 15)}{pad(ticket.submitter.name, 65)}\n"
        f"{pad('Assigned to: ', 15)}{pad(ticket.assignee.name, 65)}\n"
        f"{pad('Status: ', 15)}{pad(ticket.status.capitalize(), 65)}\n"
        f"{'*' * 80}\n")
def zenpy_update_ticket(output, text_detected_body, images_detected_body):

    credentials = {
        'email': os.environ['ZENDESK_EMAIL'],
        'token': os.environ['ZENDESK_TOKEN'],
        'subdomain': os.environ['ZENDESK_SUBDOMAIN']
    }

    zenpy_client = Zenpy(**credentials)

    ticket = zenpy_client.tickets(id=output['attachment_data']['ticket_id'])

    ticket.comment = Comment(
        body=text_detected_body,
        html_body=
        '<h4>Attachment processed by Amazon Textract & Amazon Rekognition</h4><p>{}<p><p>{}</p>'
        .format(images_detected_body, text_detected_body),
        public=False)

    zenpy_client.tickets.update(ticket)

    return
コード例 #12
0
def resend_notifications():
    start = datetime.datetime.strptime('2018-03-29 00:00:00',
                                       '%Y-%m-%d %H:%M:%S')
    zenpy_client = Zenpy(**settings.ZENDESK)

    presc = MedicalPrescription.objects.filter(
        status=MedicalPrescription.PATIENT_REQUESTED, created__gte=start)

    for p in presc:
        try:
            print(p)
            ticket = zenpy_client.tickets(id=p.zendeskticket.external_id)
            if ticket.status == 'solved':
                p.status = [
                    i.get('value') for i in ticket.custom_fields
                    if i.get('id') == 114103586991
                ][0].upper()
                if p.status == 'PATIENT_REQUESTED':
                    p.status = MedicalPrescription.UNREADABLE_PICTURES
                p.save()
                print('{}:{} - {}'.format(p.patient.user.email,
                                          p.patient.full_name, p.status))
        except Exception:
            print(p)
コード例 #13
0
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'}
コード例 #14
0
ファイル: service.py プロジェクト: lukeburden/django-zengo
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
コード例 #15
0
def view(ticketId):
  # Default
  zenpy_client = Zenpy(**creds)
  ticket = zenpy_client.tickets(id=ticketId)
  return render_template('view.html', ticket=ticket)