diff --git a/engage/hamls/msgs/message_box.haml b/engage/hamls/msgs/message_box.haml index e4b24c2b2..913cb82a6 100644 --- a/engage/hamls/msgs/message_box.haml +++ b/engage/hamls/msgs/message_box.haml @@ -210,7 +210,7 @@ %td .flex.w-full.items-end.justify-end.pr-4 - .time.whitespace-nowrap.eng + %a.time.whitespace-nowrap.eng{href:'{% url "msgs.msg_read" object.uuid %}'} {% format_datetime object.created_on %} -if show_channel_logs and not user_org.is_anon or perms.contacts.contact_break_anon diff --git a/engage/hamls/msgs/msg_read.haml b/engage/hamls/msgs/msg_read.haml index 30d167b1d..d14ed565b 100644 --- a/engage/hamls/msgs/msg_read.haml +++ b/engage/hamls/msgs/msg_read.haml @@ -8,108 +8,178 @@ temba-button { display: block; } + li { + list-style-type: disc; + } + .data-field { + display: flex; + margin-bottom: 1rem; + } + .data-label { + font-weight: bolder; + width: 12%; + } + .data-value { + font-weight: none; + } + .button-success { + background: lightgreen; + } -block page-title - {{title}} + {{ page_title }} -block title-icon %span.title-icon .glyph.icon-inbox -block title - .flex.flex-wrap.flex-row.items-center - .mr-3 - "Message Details" + "Message Details" + %hr -block subtitle - .flex.flex-wrap.mt-1 - {{ object.uuid }} - - --block buttons - --block page-top + {% if object.direction == 'O' %}OUTGOING{% else %}INCOMING{% endif %} -block content - + %hr + %br %div.sms.object - %p - id: - %span - {{object.id}} - uuid: - %span - {{object.uuid}} - contact: - %span.whitespace-nowrap - {{ object.contact|name_or_urn:user_org }} - .time.whitespace-nowrap - -block message_time - {% short_datetime object.created_on %} - -if show_channel_logs and not user_org.is_anon - .inline-block.text-gray-400.linked.ml-3 - {% channel_log_link object %} - %p - %span.msg_text - {% get_value object 'text' %} - {% if object.attachments %} - .value-attachments{ style:"margin-top: 5px" } - - for attachment in object.attachments - {% attachment_button attachment %} - {% endif %} - - .data-field - {{ object.id }} - .data-field - {{ object.uuid }} - .data-field - {{ object.text }} - .data-field - {{ object.high_priority }} - .data-field - {{ object.created_on }} - .data-field - {{ object.modified_on }} - .data-field - {{ object.sent_on }} - .data-field - {{ object.queued_on }} .data-field - {{ object.direction }} - .data-field - {{ object.status }} - .data-field - {{ object.visibility }} - .data-field - {{ object.msg_type }} - .data-field - {{ object.msg_count }} - .data-field - {{ object.error_count }} - .data-field - {{ object.next_attempt }} - .data-field - {{ object.external_id }} - .data-field - {{ object.attachments }} - .data-field - {{ object.metadata }} - .data-field - {{ object.broadcast_id }} - .data-field - {{ object.channel_id }} - .data-field - {{ object.contact_id }} - .data-field - {{ object.contact_urn_id }} - .data-field - {{ object.org_id }} - .data-field - {{ object.topup_id }} - .data-field - {{ object.failed_reason }} + .data-label + UUID: + .data-value + {{ object.uuid }} + .data-field + .data-label + ID: + .data-value + {{ object.id }} + .data-field + .data-label + Contact: + .data-value + %div + {{ object.contact|urn:user_org }} + {% if object.contact|name:user_org %} + %div + {{ object.contact|name:user_org }} + {% endif %} + %hr + .data-field + + .data-label + Text: + .data-value + {{ msg_text }} + %hr + .data-field + .data-label + Attachments: + .data-value + {% if object.attachments %} + .value-attachments{ style:"margin-top: 5px" } + - for attachment in object.attachments + {% attachment_button attachment %} + {% else %} + None + {% endif %} + .data-field + .data-label + Labels: + .data-value + .labels.flex.items-center.flex-wrap + -for label in object.labels.all + .lbl.linked.ml-2 + {{ label.name }} + .data-field + .data-label + Channel: + .data-value + {{ object.channel }}
+ address: {{ object.channel.address }}
+ id: {{ object.channel_id }} + {% if show_channel_logs and not user_org.is_anon %} + .data-field + .data-label + {% if err_channel_logs %} + Errors: + .inline-block.text-gray-400.linked.ml-3.button.button-danger + {% channel_log_link object %} + {% else %} + Channel Logs: + {% endif %} + .data-value + {% if err_channel_logs %} + -block log-entries + {% for log in err_channel_logs %} + -include "channels/channellog_log.haml" + {% endfor %} + {% else %} + .inline-block.linked.ml-3.button.button-success + {% channel_log_link object %} + {% endif %} + {% endif %} + {% if object.failed_reason %} .data-field - {{ object.flow_id }} + .data-label + Failed Reason: + .data-value + {{ object.failed_reason }} + {% endif %} + {% for msg_ts in msg_timestamps %} + .data-field.flex + .data-label + {{ msg_ts.label }} + %ul.data-value + %li + {% format_datetime msg_ts.value %} + %li + {% short_datetime msg_ts.value %} + %li + {{ msg_ts.value|date:"c" }} + %li + {{ msg_ts.value|date:"r" }} + {% endfor %} + .data-field + .data-label + Status: + .data-value + {{ msg_status }} + .data-field + .data-label + Visibility: + .data-value + {{ msg_visibility }} + .data-field + .data-label + Message Type: + .data-value + {{ msg_type }} + .data-field + .data-label + Flow ID: + .data-value + {{ object.flow_id }} + .data-field + .data-label + Is High Priority: + .data-value + {{ object.high_priority }} + .data-field + .data-label + External ID: + .data-value + {{ object.external_id }} + .data-field + .data-label + Metadata: + .data-value + {{ object.metadata }} + .data-field + .data-label + Broadcast ID: + .data-value + {{ object.broadcast_id }} -block extra-script {{ block.super }} diff --git a/engage/msgs/views/read.py b/engage/msgs/views/read.py index 6e660400b..54269af8a 100644 --- a/engage/msgs/views/read.py +++ b/engage/msgs/views/read.py @@ -5,26 +5,41 @@ from smartmin.views import SmartReadView from engage.utils.middleware import RedirectTo -from temba.msgs.models import Msg +from engage.utils.strings import sanitize_text +from temba.api.v2.serializers import MsgReadSerializer +from temba.msgs.models import Msg from temba.orgs.views import OrgPermsMixin class Read(OrgPermsMixin, SmartReadView): """ - Choices are: attachments, broadcast, broadcast_id, channel, channel_id, channel_logs, contact, contact_id, contact_urn, contact_urn_id, created_on, direction, error_count, external_id, failed_reason, flow, flow_id, high_priority, id, labels, metadata, modified_on, msg_count, msg_type, next_attempt, org, org_id, queued_on, sent_on, status, text, topup, topup_id, uuid, visibility + Choices are: attachments, broadcast, broadcast_id, + channel, channel_id, channel_logs, contact, contact_id, contact_urn, contact_urn_id, reated_on, + direction, error_count, external_id, failed_reason, flow, flow_id, high_priority, id, labels, + metadata, modified_on, msg_count, msg_type, next_attempt, org, org_id, queued_on, sent_on, status, + text, topup, topup_id, uuid, visibility """ slug_url_kwarg = "uuid" - fields = ("text",) + fields = Msg._meta.get_fields() select_related = ("org", "contact", "current_flow",) title = _("Message Details") template_name = "msgs/msg_read.haml" + show_channel_logs = True + VISIBILITIES = { # deleted messages should never be exposed over API + Msg.VISIBILITY_VISIBLE: "visible", + Msg.VISIBILITY_ARCHIVED: "archived", + Msg.VISIBILITY_DELETED_BY_SENDER: "deleted by sender", + Msg.VISIBILITY_DELETED_BY_USER: "deleted by user", + } + logger = logging.getLogger() def get_gear_links(self): links = [] #user = self.get_user() return links + #enddef get_gear_links def has_permission(self, request: WSGIRequest, *args, **kwargs): user = self.get_user() @@ -47,4 +62,83 @@ def has_permission(self, request: WSGIRequest, *args, **kwargs): #endif #enddef + def get_queryset(self, **kwargs): + qs = super().get_queryset() + if self.show_channel_logs: + qs = qs.prefetch_related("channel_logs") + return qs + #enddef get_queryset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + obj = self.get_object() + context['page_title'] = self.title + + org = self.request.user.get_org() + context["org"] = org + + if self.show_channel_logs: + if obj.channel_logs: + err_logs = sorted(obj.channel_logs.filter(is_error=True), key=lambda l: l.created_on, reverse=True) + context["err_channel_logs"] = err_logs if err_logs and len(err_logs) > 0 else None + msg_logs = sorted(obj.channel_logs.all(), key=lambda l: l.created_on, reverse=True) + context["msg_channel_logs"] = msg_logs if msg_logs and len(msg_logs) > 0 else None + context["show_channel_logs"] = msg_logs or err_logs + else: + context["msg_channel_logs"] = None + context["show_channel_logs"] = False + #endif + + msg_text = obj.text + if msg_text: + # Ensure HTML in messages does not bork our display. + if isinstance(msg_text, str): + msg_text = sanitize_text(msg_text) + elif isinstance(msg_text, dict): + for key, val in msg_text.items(): + if isinstance(val, str): + msg_text[key] = sanitize_text(val) + #endif + #endfor + #endif + #endif + context["msg_text"] = msg_text + + msg_timestamps = [{ + 'label': "Created:", + 'value': obj.created_on, + }] + if obj.queued_on: + msg_timestamps.append({ + 'label': "Queued:", + 'value': obj.queued_on, + }) + #endif + if obj.sent_on: + msg_timestamps.append({ + 'label': "Sent:", + 'value': obj.sent_on, + }) + #endif + if obj.modified_on and obj.modified_on != obj.created_on: + msg_timestamps.append({ + 'label': "Modified:", + 'value': obj.modified_on, + }) + #endif + if obj.next_attempt: + msg_timestamps.append({ + 'label': "Next attempt:", + 'value': obj.next_attempt, + }) + #endif + context['msg_timestamps'] = msg_timestamps + context['msg_status'] = MsgReadSerializer.STATUSES.get(obj.status) + context['msg_visibility'] = self.VISIBILITIES.get(obj.visibility) + context['msg_type'] = MsgReadSerializer.TYPES.get(obj.msg_type) + + #self.logger.debug("context=", extra={'context': context}) + return context + #enddef get_context_data + #endclass Read