Added zendesk submission flow

This commit is contained in:
Adrià Casajús 2022-02-09 12:00:48 +01:00
parent 219d5b998f
commit e57dcac2d2
No known key found for this signature in database
GPG Key ID: F0033226A5AFC9B9
5 changed files with 102 additions and 15 deletions

View File

@ -412,3 +412,5 @@ PHONE_PROVIDER_1_SECRET = os.environ.get("PHONE_PROVIDER_1_SECRET")
PHONE_PROVIDER_2_HEADER = os.environ.get("PHONE_PROVIDER_2_HEADER") PHONE_PROVIDER_2_HEADER = os.environ.get("PHONE_PROVIDER_2_HEADER")
PHONE_PROVIDER_2_SECRET = os.environ.get("PHONE_PROVIDER_2_SECRET") PHONE_PROVIDER_2_SECRET = os.environ.get("PHONE_PROVIDER_2_SECRET")
ZENDESK_HOST=os.environ.get('ZENDESK_HOST', 'noone.zendesk.com')

View File

@ -1,27 +1,60 @@
import json import json
import urllib.parse
from typing import Union
import requests import requests
from flask import render_template, request, flash, url_for, redirect from flask import render_template, request, flash, url_for, redirect
from flask_login import login_required, current_user from flask_login import login_required, current_user
from werkzeug.datastructures import FileStorage
from app.dashboard.base import dashboard_bp from app.dashboard.base import dashboard_bp
from app.db import Session
from app.log import LOG from app.log import LOG
from app.models import Alias, Mailbox from app.models import Mailbox
from app.config import ZENDESK_HOST
VALID_MIME_TYPES = ['text/plain', 'message/rfc822']
@dashboard_bp.route("/support", methods=["GET"]) @dashboard_bp.route("/support", methods=["GET"])
@login_required @login_required
def show_support_dialog(): def show_support_dialog():
return render_template( "dashboard/support.html" ) mailbox = Mailbox.get(current_user.default_mailbox_id)
return render_template("dashboard/support.html", ticketEmail=mailbox.email)
def createZendeskTicket(email: str, contents: str): def upload_file_to_zendesk(file: FileStorage) -> Union[None, str]:
if file.mimetype not in VALID_MIME_TYPES and not file.mimetype.startswith('image/'):
flash('File {} is not an image, text or an email'.format(file.filename), "warning")
return None
escaped_filename = urllib.parse.urlencode({'filename': file.filename})
url = 'https://{}/api/v2/uploads?{}'.format(ZENDESK_HOST, escaped_filename)
headers = {'content-type': file.mimetype}
response = requests.post(url, headers=headers, data=file.stream)
if response.status_code != 201:
if response.status_code == 401 or 422:
LOG.debug('Could not authenticate')
return None
else:
LOG.debug('Problem with the request. Status ' + str(response.status_code))
return None
data = response.json()
return data['upload']['token']
def create_zendesk_request(email: str, contents: str, files: [FileStorage]) -> bool:
tokens = []
for file in files:
token = upload_file_to_zendesk(file)
if token is None:
return False
tokens.append(token)
data = { data = {
'request': { 'request': {
'subject': 'Ticket created for user {}'.format(current_user.id), 'subject': 'Ticket created for user {}'.format(current_user.id),
'comment': { 'comment': {
'type': 'Comment', 'type': 'Comment',
'body': contents 'body': contents,
'uploads': tokens
}, },
'requester': { 'requester': {
'name': "SimpleLogin user {}".format(current_user.id), 'name': "SimpleLogin user {}".format(current_user.id),
@ -29,17 +62,19 @@ def createZendeskTicket(email: str, contents: str):
} }
} }
} }
url = 'https://simplelogin.zendesk.com/api/v2/requests.json' url = 'https://{}/api/v2/requests.json'.format(ZENDESK_HOST)
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
r = requests.post(url, data=json.dumps(data), headers=headers) r = requests.post(url, data=json.dumps(data), headers=headers)
if r.status_code != 201: if r.status_code != 201:
if r.status_code == 401 or 422: if r.status_code == 401 or 422:
LOG.debug('Could not authenticate') LOG.debug('Could not authenticate')
return False
else: else:
LOG.debug('Problem with the request. Status ' + str(r.status_code)) LOG.debug('Problem with the request. Status ' + str(r.status_code))
else: return False
flash("Ticket was created. You should receive an email notification", "success") flash("Ticket was created. You should receive an email notification", "success")
LOG.debug('Ticket created') LOG.debug('Ticket created')
return True
@dashboard_bp.route("/support", methods=["POST"]) @dashboard_bp.route("/support", methods=["POST"])
@ -53,5 +88,6 @@ def process_support_dialog():
if not email: if not email:
flash("Please add an email", "warning") flash("Please add an email", "warning")
return render_template("dashboard/support.html", ticketContents=contents) return render_template("dashboard/support.html", ticketContents=contents)
createZendeskTicket(email, contents) if create_zendesk_request(email, contents, request.files.getlist('ticketFiles')):
return redirect(url_for('dashboard.index')) return render_template("dashboard/support_ticket_created.html", ticketEmail=email)
return render_template("dashboard/support.html", ticketEmail=email, ticketContents=contents)

View File

@ -19,7 +19,7 @@
<script> <script>
$( document ).ready(function() { $( document ).ready(function() {
var app = new Vue({ var app = new Vue({
el: '#supportZendeskForm', el: '#aliasGroup',
data: { data: {
ticketEmail: document.querySelector("input[name=ticketEmail]").value ticketEmail: document.querySelector("input[name=ticketEmail]").value
}, },
@ -33,6 +33,16 @@
} }
} }
}); });
$('.custom-file input').change(function (e) {
var files = [];
for (var i = 0; i < $(this)[0].files.length; i++) {
files.push($(this)[0].files[i].name);
}
$(this).next('.custom-file-label').html(files.join(', '));
});
}) })
</script> </script>
{% endblock %} {% endblock %}
@ -50,7 +60,7 @@
<div class="mt-2"> <div class="mt-2">
A support ticket will be created in Zendesk. Please do not include any sensitive information in the ticket. A support ticket will be created in Zendesk. Please do not include any sensitive information in the ticket.
</div> </div>
<form id="supportZendeskForm" method="post"> <form id="supportZendeskForm" method="post" enctype="multipart/form-data">
<div class="mt-4 mb-5"> <div class="mt-4 mb-5">
<label for="issueDescription" class="form-label">What happened?</label> <label for="issueDescription" class="form-label">What happened?</label>
<textarea class="form-control" name="ticketContents" id="issueDescription" rows="3" placeholder="Please provide as much information as possible. For example which alias(es), mailbox(es) ar affected, if this is a persistent issue...">{{- ticketContents or '' -}}</textarea> <textarea class="form-control" name="ticketContents" id="issueDescription" rows="3" placeholder="Please provide as much information as possible. For example which alias(es), mailbox(es) ar affected, if this is a persistent issue...">{{- ticketContents or '' -}}</textarea>
@ -58,8 +68,9 @@
<div class="mt-5 font-weight-bold"> <div class="mt-5 font-weight-bold">
Attach files to support request Attach files to support request
</div> </div>
<div class="mt-1 text-muted">Only images, text and emails are accepted</div>
<div class="custom-file mt-2"> <div class="custom-file mt-2">
<input type="file" class="custom-file-input" name="ticketFiles" id="ticketFileGroup" multiple> <input type="file" class="custom-file-input" name="ticketFiles" id="ticketFileGroup" multiple="multiple">
<label class="custom-file-label" for="ticketFileGroup">Choose file</label> <label class="custom-file-label" for="ticketFileGroup">Choose file</label>
</div> </div>
<div class="mt-5 font-weight-bold"> <div class="mt-5 font-weight-bold">
@ -68,7 +79,7 @@
<div class="mt-2"> <div class="mt-2">
Conversations related to this ticket will be sent to this address. Feel free to use an alias here. Conversations related to this ticket will be sent to this address. Feel free to use an alias here.
</div> </div>
<div class="input-group mb-3"> <div class="input-group mb-3" id="aliasGroup">
<input type="text" class="form-control" placeholder="Email" value="{{ ticketEmail }}" name="ticketEmail" v-model='ticketEmail' aria-label="Email to send responses to" aria-describedby="button-addon2"> <input type="text" class="form-control" placeholder="Email" value="{{ ticketEmail }}" name="ticketEmail" v-model='ticketEmail' aria-label="Email to send responses to" aria-describedby="button-addon2">
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-outline-primary" type="button" @click="generateRandomAlias" id="button-addon2">Generate a random alias</button> <button class="btn btn-outline-primary" type="button" @click="generateRandomAlias" id="button-addon2">Generate a random alias</button>

View File

@ -0,0 +1,37 @@
{% extends 'default.html' %}
{% set active_page = None %}
{% block title %}
Support
{% endblock %}
{% block head %}
<style>
.card-title {
font-size: 22px;
font-weight: 600;
margin-bottom: 3px;
}
</style>
{% endblock %}
{% block default_content %}
<div class="col pb-3">
<!-- Current plan -->
<div class="card">
<div class="card-body">
<div class="card-title mb-3">Support ticket has been created for {{ ticketEmail }}</div>
<div class="mt-2">
Head back to <a href="/dashboard">your dashboard</a>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -66,6 +66,7 @@
href="https://github.com/simple-login/app/projects/1">Roadmap</a></li> href="https://github.com/simple-login/app/projects/1">Roadmap</a></li>
<li><a class="list-group-item text-white footer-item" <li><a class="list-group-item text-white footer-item"
href="https://simplelogin.io/contact/">Contact Us</a></li> href="https://simplelogin.io/contact/">Contact Us</a></li>
<li><a class="list-group-item text-white footer-item" href="/dashboard/support">Support</a></li>
<li><a class="list-group-item text-white footer-item" <li><a class="list-group-item text-white footer-item"
href="https://simplelogin.io/imprint/">Imprint</a></li> href="https://simplelogin.io/imprint/">Imprint</a></li>
</ul> </ul>