Use plain Bulma themes when possible
This commit is contained in:
@@ -150,9 +150,11 @@
|
||||
{% if variant.warn_verbatim_plan %}
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
<article class="command-variant-warning">
|
||||
<article class="message is-warning is-light">
|
||||
<div class="message-body is-size-7 has-text-dark">
|
||||
<strong>Warning:</strong> <strong>{{ variant.variant_label }}</strong> is in <code>verbatim</code> mode with plan fanout enabled.
|
||||
Recipients will get raw transcript-style output.
|
||||
</div>
|
||||
</article>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -188,15 +190,17 @@
|
||||
|
||||
<h4 class="title is-7" style="margin-top: 0.95rem;">Effective Destinations</h4>
|
||||
{% if profile.enabled_egress_bindings %}
|
||||
<ul class="command-destination-list is-size-7">
|
||||
<div class="content is-size-7" style="margin-bottom: 0.4rem;">
|
||||
{% for row in profile.enabled_egress_bindings %}
|
||||
<li class="command-destination-item">
|
||||
<p style="margin-bottom: 0.3rem;">
|
||||
<span class="tag is-link is-light is-rounded is-small">{{ row.service }}</span>
|
||||
<code>{{ row.channel_identifier }}</code>
|
||||
</li>
|
||||
</p>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="command-destination-summary">{{ profile.enabled_egress_bindings|length }} enabled egress destination{{ profile.enabled_egress_bindings|length|pluralize }}.</p>
|
||||
</div>
|
||||
<article class="notification is-info is-light is-size-7" style="padding: 0.5rem 0.7rem;">
|
||||
{{ profile.enabled_egress_bindings|length }} enabled egress destination{{ profile.enabled_egress_bindings|length|pluralize }}.
|
||||
</article>
|
||||
{% else %}
|
||||
<article class="notification is-warning is-light is-size-7">No enabled egress destinations. Plan fanout will show sent:0.</article>
|
||||
{% endif %}
|
||||
@@ -441,51 +445,5 @@
|
||||
.command-order-capsule-form + .command-order-capsule-form .command-order-btn {
|
||||
border-top: 1px solid #dbdbdb;
|
||||
}
|
||||
.command-variant-warning {
|
||||
border: 1px solid rgba(171, 109, 17, 0.45);
|
||||
background: linear-gradient(180deg, rgba(255, 246, 226, 0.98), rgba(255, 238, 204, 0.95));
|
||||
color: #6e450e;
|
||||
border-radius: 8px;
|
||||
padding: 0.48rem 0.62rem;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.command-variant-warning strong {
|
||||
color: #3f2a09;
|
||||
}
|
||||
.command-variant-warning code {
|
||||
color: #5b3a0c;
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
.command-destination-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.28rem;
|
||||
}
|
||||
.command-destination-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.42rem;
|
||||
background: rgba(244, 248, 255, 0.85);
|
||||
border: 1px solid rgba(58, 103, 165, 0.2);
|
||||
border-radius: 7px;
|
||||
padding: 0.26rem 0.38rem;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
.command-destination-summary {
|
||||
margin-top: 0.44rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
background: rgba(239, 247, 255, 0.95);
|
||||
border: 1px solid rgba(58, 103, 165, 0.25);
|
||||
padding: 0.16rem 0.52rem;
|
||||
font-size: 0.73rem;
|
||||
color: #284d7c;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -25,7 +25,19 @@
|
||||
<div class="has-text-grey"><code>{{ row.actor_identifier }}</code></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><code>{{ row.payload }}</code></td>
|
||||
<td>
|
||||
{% if row.payload_view.summary_items %}
|
||||
<div class="tags" style="margin-bottom: 0.35rem;">
|
||||
{% for item in row.payload_view.summary_items %}
|
||||
<span class="tag task-ui-badge"><strong>{{ item.0 }}</strong>: {{ item.1 }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<details>
|
||||
<summary class="is-size-7 has-text-link" style="cursor:pointer;">View payload JSON</summary>
|
||||
<pre class="task-event-payload">{{ row.payload_view.pretty_text }}</pre>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No events.</td></tr>
|
||||
@@ -47,4 +59,23 @@
|
||||
</table>
|
||||
</article>
|
||||
</div></section>
|
||||
<style>
|
||||
.task-event-payload {
|
||||
margin-top: 0.35rem;
|
||||
padding: 0.6rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(127, 127, 127, 0.25);
|
||||
background: rgba(245, 245, 245, 0.75);
|
||||
color: #1f1f1f;
|
||||
max-width: 72ch;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
[data-theme="dark"] .task-event-payload {
|
||||
background: rgba(35, 35, 35, 0.75);
|
||||
color: #f5f5f5;
|
||||
border-color: rgba(200, 200, 200, 0.35);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,6 +7,7 @@ from core.models import (
|
||||
ChatSession,
|
||||
ChatTaskSource,
|
||||
DerivedTask,
|
||||
DerivedTaskEvent,
|
||||
Message,
|
||||
Person,
|
||||
PersonIdentifier,
|
||||
@@ -154,3 +155,38 @@ class TasksPagesManagementTests(TestCase):
|
||||
response = self.client.get(reverse("tasks_hub"))
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertContains(response, "Scope Person")
|
||||
|
||||
def test_task_detail_renders_payload_summary_and_json(self):
|
||||
project = TaskProject.objects.create(user=self.user, name="Payload Test")
|
||||
session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
|
||||
origin = Message.objects.create(
|
||||
user=self.user,
|
||||
session=session,
|
||||
ts=1_700_000_000_000,
|
||||
text="origin",
|
||||
sender_uuid="+15551230000",
|
||||
custom_author="OTHER",
|
||||
source_service="signal",
|
||||
source_chat_id="+15551230000",
|
||||
)
|
||||
task = DerivedTask.objects.create(
|
||||
user=self.user,
|
||||
project=project,
|
||||
title="Payload detail",
|
||||
source_service="signal",
|
||||
source_channel="+15551230000",
|
||||
origin_message=origin,
|
||||
reference_code="42",
|
||||
status_snapshot="open",
|
||||
)
|
||||
DerivedTaskEvent.objects.create(
|
||||
task=task,
|
||||
event_type="reaction_captured",
|
||||
payload={"source": "signal", "emoji": "❤️", "reason": "heart_reaction"},
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("tasks_task", kwargs={"task_id": str(task.id)}))
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertContains(response, "View payload JSON")
|
||||
self.assertContains(response, "<strong>source</strong>: signal", html=True)
|
||||
self.assertContains(response, ""emoji": "❤️"")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
@@ -174,6 +175,60 @@ def _service_label(service: str) -> str:
|
||||
return labels.get(key, key.title() if key else "Unknown")
|
||||
|
||||
|
||||
def _format_task_event_payload(raw_payload):
|
||||
payload = raw_payload
|
||||
if payload is None:
|
||||
payload = {}
|
||||
if isinstance(payload, str):
|
||||
text = payload.strip()
|
||||
if not text:
|
||||
return {
|
||||
"summary_items": [],
|
||||
"pretty_text": "{}",
|
||||
"is_mapping": False,
|
||||
}
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
payload = parsed
|
||||
except Exception:
|
||||
return {
|
||||
"summary_items": [("text", text[:140])],
|
||||
"pretty_text": text,
|
||||
"is_mapping": False,
|
||||
}
|
||||
|
||||
if isinstance(payload, dict):
|
||||
summary = []
|
||||
preferred = ("source", "reason", "reaction", "emoji", "presence", "last_seen_ts")
|
||||
for key in preferred:
|
||||
if key in payload:
|
||||
summary.append((key, str(payload.get(key))))
|
||||
if not summary:
|
||||
for key in list(payload.keys())[:4]:
|
||||
summary.append((str(key), str(payload.get(key))))
|
||||
pretty = json.dumps(payload, indent=2, ensure_ascii=False, sort_keys=True)
|
||||
return {
|
||||
"summary_items": summary,
|
||||
"pretty_text": pretty,
|
||||
"is_mapping": True,
|
||||
}
|
||||
|
||||
if isinstance(payload, (list, tuple)):
|
||||
pretty = json.dumps(list(payload), indent=2, ensure_ascii=False)
|
||||
return {
|
||||
"summary_items": [("items", str(len(payload)))],
|
||||
"pretty_text": pretty,
|
||||
"is_mapping": False,
|
||||
}
|
||||
|
||||
text = str(payload)
|
||||
return {
|
||||
"summary_items": [("value", text[:140])],
|
||||
"pretty_text": text,
|
||||
"is_mapping": False,
|
||||
}
|
||||
|
||||
|
||||
def _creator_label_for_message(user, service: str, message) -> str:
|
||||
msg = message
|
||||
if msg is None:
|
||||
@@ -800,6 +855,7 @@ class TaskDetail(LoginRequiredMixin, View):
|
||||
raw_actor = str(getattr(row, "actor_identifier", "") or "").strip()
|
||||
if raw_actor:
|
||||
row.actor_display = raw_actor
|
||||
row.payload_view = _format_task_event_payload(getattr(row, "payload", None))
|
||||
task.creator_label = _creator_label_for_message(
|
||||
request.user,
|
||||
str(getattr(task, "source_service", "") or "").strip().lower(),
|
||||
|
||||
Reference in New Issue
Block a user