From f213469e9f9bd97f9b06d43b0bf24e48ff610ab4 Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Wed, 26 Aug 2020 11:44:54 +0200 Subject: [PATCH] display nb-reply, nb-forward on alias contact page --- app/api/serializer.py | 2 +- .../dashboard/alias_contact_manager.html | 51 ++++++--- app/dashboard/templates/dashboard/index.html | 2 +- app/dashboard/views/alias_contact_manager.py | 102 ++++++++++++++++-- app/models.py | 26 +---- 5 files changed, 133 insertions(+), 50 deletions(-) diff --git a/app/api/serializer.py b/app/api/serializer.py index bb42ba96..c22dceab 100644 --- a/app/api/serializer.py +++ b/app/api/serializer.py @@ -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 ) ) ) diff --git a/app/dashboard/templates/dashboard/alias_contact_manager.html b/app/dashboard/templates/dashboard/alias_contact_manager.html index e9c1bd21..a05fefe8 100644 --- a/app/dashboard/templates/dashboard/alias_contact_manager.html +++ b/app/dashboard/templates/dashboard/alias_contact_manager.html @@ -61,9 +61,17 @@
- {% for contact in contacts %} + {% for contact_info in contact_infos %} + {% set contact = contact_info.contact %}
+
+ {{ contact.website_email }} + {% if contact.pgp_finger_print %} + 🗝 + {% endif %} +
+
************************* Copy reverse-alias
-
- Contact {{ contact.website_email }} - {% if contact.pgp_finger_print %} - 🗝 - {% endif %} -
+
- Created {{ contact.created_at | dt }}
+ {% 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 %} + + {{ email_log.created_at | dt }} + {% elif email_log.bounced %} + + + {{ email_log.created_at | dt }} + + {% elif email_log.blocked %} + + {{ email_log.created_at | dt }} + {% else %} + + {{ email_log.created_at | dt }} + {% endif %} +
+ Contact created {{ contact.created_at | dt }} + {% else %} + No Activity. Contact created {{ contact.created_at | dt }} {% endif %} + +
+ {{ contact_info.nb_forward }} forwards, + {{ contact_info.nb_reply }} replies +
Edit ➡ @@ -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', diff --git a/app/dashboard/templates/dashboard/index.html b/app/dashboard/templates/dashboard/index.html index 263c206c..0fbda3b1 100644 --- a/app/dashboard/templates/dashboard/index.html +++ b/app/dashboard/templates/dashboard/index.html @@ -246,7 +246,7 @@ {{ email_log.created_at | dt }} {% else %} {{ contact.website_email }} - + {{ email_log.created_at | dt }} {% endif %} {% else %} diff --git a/app/dashboard/views/alias_contact_manager.py b/app/dashboard/views/alias_contact_manager.py index cf899136..363eb975 100644 --- a/app/dashboard/views/alias_contact_manager.py +++ b/app/dashboard/views/alias_contact_manager.py @@ -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//", 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, diff --git a/app/models.py b/app/models.py index 867f146b..48759460 100644 --- a/app/models.py +++ b/app/models.py @@ -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""