display nb-reply, nb-forward on alias contact page

This commit is contained in:
Son NK 2020-08-26 11:44:54 +02:00
parent a1206d212f
commit f213469e9f
5 changed files with 133 additions and 50 deletions

View File

@ -244,7 +244,7 @@ def get_alias_infos_with_pagination_v3(
.filter( .filter(
or_( or_(
EmailLog.created_at == sub.c.max_created_at, EmailLog.created_at == sub.c.max_created_at,
sub.c.max_created_at == None, sub.c.max_created_at == None, # no email log yet for this alias
) )
) )
) )

View File

@ -61,9 +61,17 @@
</form> </form>
<div class="row"> <div class="row">
{% for contact in contacts %} {% for contact_info in contact_infos %}
{% set contact = contact_info.contact %}
<div class="col-md-6"> <div class="col-md-6">
<div class="my-2 p-2 card {% if contact.id == highlight_contact_id %} highlight-row {% endif %}"> <div class="my-2 p-2 card {% if contact.id == highlight_contact_id %} highlight-row {% endif %}">
<div class="mb-2">
<span class="font-weight-bold">{{ contact.website_email }}</span>
{% if contact.pgp_finger_print %}
<span class="cursor" data-toggle="tooltip" data-original-title="PGP Enabled">🗝</span>
{% endif %}
</div>
<div> <div>
<span> <span>
<a href="{{ 'mailto:' + contact.website_send_to() }}" <a href="{{ 'mailto:' + contact.website_send_to() }}"
@ -72,26 +80,45 @@
class="font-weight-bold">*************************</a> class="font-weight-bold">*************************</a>
<span class="clipboard btn btn-sm btn-success copy-btn" data-toggle="tooltip" <span class="clipboard btn btn-sm btn-success copy-btn" data-toggle="tooltip"
title="Copy to clipboard" title="Copy the reverse-alias to clipboard"
data-clipboard-text="{{ contact.website_send_to() }}"> data-clipboard-text="{{ contact.website_send_to() }}">
Copy reverse-alias Copy reverse-alias
</span> </span>
</span> </span>
</div> </div>
<div>
Contact <b>{{ contact.website_email }}</b>
{% if contact.pgp_finger_print %}
<span class="cursor" data-toggle="tooltip" data-original-title="PGP Enabled">🗝</span>
{% endif %}
</div>
<div class="mb-2 text-muted small-text"> <div class="mb-2 text-muted small-text">
Created {{ contact.created_at | dt }} <br> {% if contact_info.latest_email_log != None %}
{% set email_log = contact_info.latest_email_log %}
{% if contact.latest_reply %} {% if email_log.is_reply %}
Last email sent {{ contact.latest_reply | dt }} <i class="fa fa-reply mr-2" data-toggle="tooltip" title="Email reply/sent from alias"></i>
{{ email_log.created_at | dt }}
{% elif email_log.bounced %}
<span class="text-danger">
<i class="fa fa-warning mr-2" data-toggle="tooltip"
title="Email bounced and cannot be forwarded to your mailbox"></i>
{{ email_log.created_at | dt }}
</span>
{% elif email_log.blocked %}
<i class="fa fa-ban mr-2 text-danger" data-toggle="tooltip" title="Email blocked"></i>
{{ email_log.created_at | dt }}
{% else %}
<i class="fa fa-paper-plane mr-2" data-toggle="tooltip" title="Email sent to alias"></i>
{{ email_log.created_at | dt }}
{% endif %}
<br>
Contact created {{ contact.created_at | dt }}
{% else %}
No Activity. Contact created {{ contact.created_at | dt }}
{% endif %} {% endif %}
<div>
<span class="alias-activity">{{ contact_info.nb_forward }}</span> forwards,
<span class="alias-activity">{{ contact_info.nb_reply }}</span> replies
</div>
</div> </div>
<a href="{{ url_for('dashboard.contact_detail_route', contact_id=contact.id) }}">Edit ➡</a> <a href="{{ url_for('dashboard.contact_detail_route', contact_id=contact.id) }}">Edit ➡</a>
@ -137,7 +164,7 @@
let that = $(this); let that = $(this);
bootbox.confirm({ bootbox.confirm({
message: "All activities associated with this reverse-alias will also be deleted, please confirm.", message: "All activities associated with this contact will also be deleted, please confirm.",
buttons: { buttons: {
confirm: { confirm: {
label: 'Yes, delete it', label: 'Yes, delete it',

View File

@ -246,7 +246,7 @@
{{ email_log.created_at | dt }} {{ email_log.created_at | dt }}
{% else %} {% else %}
{{ contact.website_email }} {{ contact.website_email }}
<i class="fa fa-paper-plane mr-2" data-toggle="tooltip" title="Email forwarded to alias"></i> <i class="fa fa-paper-plane mr-2" data-toggle="tooltip" title="Email sent to alias"></i>
{{ email_log.created_at | dt }} {{ email_log.created_at | dt }}
{% endif %} {% endif %}
{% else %} {% else %}

View File

@ -1,8 +1,12 @@
import re import re
from dataclasses import dataclass
from operator import or_
from flask import render_template, request, redirect, url_for, flash from flask import render_template, request, redirect, flash
from flask import url_for
from flask_login import login_required, current_user from flask_login import login_required, current_user
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from sqlalchemy import and_, func, case
from wtforms import StringField, validators, ValidationError from wtforms import StringField, validators, ValidationError
from app.config import EMAIL_DOMAIN, PAGE_LIMIT from app.config import EMAIL_DOMAIN, PAGE_LIMIT
@ -10,7 +14,7 @@ from app.dashboard.base import dashboard_bp
from app.email_utils import parseaddr_unicode from app.email_utils import parseaddr_unicode
from app.extensions import db from app.extensions import db
from app.log import LOG from app.log import LOG
from app.models import Alias, Contact from app.models import Alias, Contact, EmailLog
from app.utils import random_string from app.utils import random_string
@ -45,6 +49,82 @@ class NewContactForm(FlaskForm):
) )
@dataclass
class ContactInfo(object):
contact: Contact
nb_forward: int
nb_reply: int
latest_email_log: EmailLog
def get_contact_infos(alias: Alias, page=0, contact_id=None) -> [ContactInfo]:
"""if contact_id is set, only return the contact info for this contact"""
sub = (
db.session.query(
Contact.id,
func.sum(case([(EmailLog.is_reply, 1)], else_=0)).label("nb_reply"),
func.sum(
case(
[
(
and_(
EmailLog.is_reply == False, EmailLog.blocked == False,
),
1,
)
],
else_=0,
)
).label("nb_forward"),
func.max(EmailLog.created_at).label("max_email_log_created_at"),
)
.join(EmailLog, EmailLog.contact_id == Contact.id, isouter=True,)
.filter(Contact.alias_id == alias.id)
.group_by(Contact.id)
.subquery()
)
q = (
db.session.query(Contact, EmailLog, sub.c.nb_reply, sub.c.nb_forward,)
.join(EmailLog, EmailLog.contact_id == Contact.id, isouter=True,)
.filter(Contact.alias_id == alias.id)
.filter(Contact.id == sub.c.id)
.filter(
or_(
EmailLog.created_at == sub.c.max_email_log_created_at,
# no email log yet for this contact
sub.c.max_email_log_created_at == None,
)
)
)
if contact_id:
q = q.filter(Contact.id == contact_id)
latest_activity = case(
[
(EmailLog.created_at > Contact.created_at, EmailLog.created_at),
(EmailLog.created_at < Contact.created_at, Contact.created_at),
],
else_=Contact.created_at,
)
q = q.order_by(latest_activity.desc()).limit(PAGE_LIMIT).offset(page * PAGE_LIMIT)
ret = []
for contact, latest_email_log, nb_reply, nb_forward in q:
contact_info = ContactInfo(
contact=contact,
nb_forward=nb_forward,
nb_reply=nb_reply,
latest_email_log=latest_email_log,
)
ret.append(contact_info)
return ret
@dashboard_bp.route("/alias_contact_manager/<alias_id>/", methods=["GET", "POST"]) @dashboard_bp.route("/alias_contact_manager/<alias_id>/", methods=["GET", "POST"])
@login_required @login_required
def alias_contact_manager(alias_id): def alias_contact_manager(alias_id):
@ -149,20 +229,20 @@ def alias_contact_manager(alias_id):
url_for("dashboard.alias_contact_manager", alias_id=alias_id) url_for("dashboard.alias_contact_manager", alias_id=alias_id)
) )
contact_infos = get_contact_infos(alias, page)
last_page = len(contact_infos) < PAGE_LIMIT
# if highlighted contact isn't included, fetch it
# make sure highlighted contact is at array start # make sure highlighted contact is at array start
contacts = alias.get_contacts(page) contact_ids = [contact_info.contact.id for contact_info in contact_infos]
contact_ids = [contact.id for contact in contacts]
last_page = len(contacts) < PAGE_LIMIT
if highlight_contact_id not in contact_ids: if highlight_contact_id not in contact_ids:
contact = Contact.get(highlight_contact_id) contact_infos = (
if contact and contact.alias_id == alias.id: get_contact_infos(alias, contact_id=highlight_contact_id) + contact_infos
contacts.insert(0, contact) )
return render_template( return render_template(
"dashboard/alias_contact_manager.html", "dashboard/alias_contact_manager.html",
contacts=contacts, contact_infos=contact_infos,
alias=alias, alias=alias,
new_contact_form=new_contact_form, new_contact_form=new_contact_form,
highlight_contact_id=highlight_contact_id, highlight_contact_id=highlight_contact_id,

View File

@ -9,7 +9,7 @@ import bcrypt
from arrow import Arrow from arrow import Arrow
from flask import url_for from flask import url_for
from flask_login import UserMixin from flask_login import UserMixin
from sqlalchemy import text, desc, CheckConstraint, and_, func from sqlalchemy import text, desc, CheckConstraint, and_, func, case
from sqlalchemy_utils import ArrowType from sqlalchemy_utils import ArrowType
from app import s3 from app import s3
@ -915,30 +915,6 @@ class Alias(db.Model, ModelMixin):
else: else:
return self.user.email return self.user.email
def get_contacts(self, page=0):
latest_reply = func.max(EmailLog.created_at)
q = (
db.session.query(Contact, latest_reply)
.join(
EmailLog,
and_(EmailLog.contact_id == Contact.id, EmailLog.is_reply),
isouter=True,
)
.filter(Contact.alias_id == self.id)
.group_by(Contact.id)
.order_by(Contact.created_at.desc())
.limit(PAGE_LIMIT)
.offset(page * PAGE_LIMIT)
.all()
)
contacts = []
for contact, l in q:
contact.latest_reply = l
contacts.append(contact)
return contacts
def __repr__(self): def __repr__(self):
return f"<Alias {self.id} {self.email}>" return f"<Alias {self.id} {self.email}>"