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(
or_(
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>
<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="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>
<span>
<a href="{{ 'mailto:' + contact.website_send_to() }}"
@ -72,26 +80,45 @@
class="font-weight-bold">*************************</a>
<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() }}">
Copy reverse-alias
</span>
</span>
</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">
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 %}
Last email sent {{ contact.latest_reply | dt }}
{% if email_log.is_reply %}
<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 %}
<div>
<span class="alias-activity">{{ contact_info.nb_forward }}</span> forwards,
<span class="alias-activity">{{ contact_info.nb_reply }}</span> replies
</div>
</div>
<a href="{{ url_for('dashboard.contact_detail_route', contact_id=contact.id) }}">Edit ➡</a>
@ -137,7 +164,7 @@
let that = $(this);
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: {
confirm: {
label: 'Yes, delete it',

View File

@ -246,7 +246,7 @@
{{ email_log.created_at | dt }}
{% else %}
{{ 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 }}
{% endif %}
{% else %}

View File

@ -1,8 +1,12 @@
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_wtf import FlaskForm
from sqlalchemy import and_, func, case
from wtforms import StringField, validators, ValidationError
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.extensions import db
from app.log import LOG
from app.models import Alias, Contact
from app.models import Alias, Contact, EmailLog
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"])
@login_required
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)
)
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
contacts = alias.get_contacts(page)
contact_ids = [contact.id for contact in contacts]
last_page = len(contacts) < PAGE_LIMIT
contact_ids = [contact_info.contact.id for contact_info in contact_infos]
if highlight_contact_id not in contact_ids:
contact = Contact.get(highlight_contact_id)
if contact and contact.alias_id == alias.id:
contacts.insert(0, contact)
contact_infos = (
get_contact_infos(alias, contact_id=highlight_contact_id) + contact_infos
)
return render_template(
"dashboard/alias_contact_manager.html",
contacts=contacts,
contact_infos=contact_infos,
alias=alias,
new_contact_form=new_contact_form,
highlight_contact_id=highlight_contact_id,

View File

@ -9,7 +9,7 @@ import bcrypt
from arrow import Arrow
from flask import url_for
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 app import s3
@ -915,30 +915,6 @@ class Alias(db.Model, ModelMixin):
else:
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):
return f"<Alias {self.id} {self.email}>"