M scss/main.scss => scss/main.scss +26 -0
@@ 239,3 239,29 @@ select.form-control {
flex-grow: 1;
}
}
+
+// TODO: move me into core.sr.ht
+details {
+ padding: 0 1rem;
+ margin: 0 -1rem 1rem;
+
+ summary {
+ background: $gray-300;
+ padding: 0 1rem;
+ margin: 0 -1rem;
+ }
+
+ &[open] {
+ padding: 0 1rem;
+ margin-left: calc(-1rem - 4px);
+ border-left: 4px solid $gray-300;
+ }
+}
+
+.prefs {
+ padding: 0.5rem 0;
+
+ .form-check {
+ padding-left: 0;
+ }
+}
M todosrht-lmtp => todosrht-lmtp +1 -1
@@ 236,7 236,7 @@ class MailHandler:
return "550 Comment must be between 3 and 16384 characters."
event = add_comment(sender, ticket, text=body,
- resolution=resolution, resolve=resolve, reopen=reopen)
+ resolution=resolution, resolve=resolve, reopen=reopen, from_email=True)
TicketWebhook.deliver(TicketWebhook.Events.event_create,
event.to_dict(),
TicketWebhook.Subscription.ticket_id == ticket.id)
A todosrht/alembic/versions/3a9cb6757f59_add_user_notify_self.py => todosrht/alembic/versions/3a9cb6757f59_add_user_notify_self.py +23 -0
@@ 0,0 1,23 @@
+"""Add User.notify_self
+
+Revision ID: 3a9cb6757f59
+Revises: 6c714f704591
+Create Date: 2020-09-28 19:11:22.221191
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '3a9cb6757f59'
+down_revision = '6c714f704591'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('user', sa.Column('notify_self', sa.Boolean,
+ nullable=False, server_default='FALSE'))
+
+
+def downgrade():
+ op.drop_column('user', 'notify_self')
M todosrht/blueprints/html.py => todosrht/blueprints/html.py +19 -7
@@ 1,12 1,14 @@
-from flask import Blueprint, render_template, request, abort
+from flask import Blueprint, render_template, request, abort, redirect, url_for
from todosrht.access import get_tracker, get_access
from todosrht.tickets import get_participant_for_user
from todosrht.types import Tracker, Ticket, TicketAccess
from todosrht.types import Event, EventNotification, EventType
from todosrht.types import User, Participant
from srht.config import cfg
-from srht.oauth import current_user
+from srht.database import db
+from srht.oauth import current_user, loginrequired
from srht.flask import paginate_query, session
+from srht.validation import Validation
from sqlalchemy import and_, or_
html = Blueprint('html', __name__)
@@ 51,7 53,7 @@ def filter_authorized_events(events):
return events
@html.route("/")
-def index():
+def index_GET():
if not current_user:
return render_template("index.html")
trackers = (Tracker.query
@@ 68,15 70,25 @@ def index():
.order_by(Event.created.desc()))
events = events.limit(10).all()
- notice = session.get("notice")
- if notice:
- del session["notice"]
+ notice = session.pop("notice", None)
+ prefs_updated = session.pop("prefs_updated", None)
return render_template("dashboard.html",
trackers=trackers, notice=notice,
tracker_list_msg="Your Trackers",
more_trackers=total_trackers > limit_trackers,
- events=events, EventType=EventType)
+ events=events, EventType=EventType,
+ prefs_updated=prefs_updated)
+
+@html.route("/", methods=["POST"])
+@loginrequired
+def index_POST():
+ valid = Validation(request)
+ notify_self = valid.require("notify-self")
+ current_user.notify_self = notify_self == "on"
+ db.session.commit()
+ session["prefs_updated"] = True
+ return redirect(url_for("html.index_GET"))
@html.route("/~<username>")
def user_GET(username):
M todosrht/blueprints/settings.py => todosrht/blueprints/settings.py +1 -1
@@ 214,7 214,7 @@ def delete_POST(owner, name):
{ "id": tracker_id },
UserWebhook.Subscription.user_id == owner_id)
- return redirect(url_for("html.index"))
+ return redirect(url_for("html.index_GET"))
@settings.route("/<owner>/<name>/settings/import-export")
@loginrequired
M todosrht/templates/dashboard.html => todosrht/templates/dashboard.html +22 -0
@@ 51,6 51,28 @@
>
Create new tracker {{icon("caret-right")}}
</a>
+ <details
+ style="margin: -0.5rem 0 0.5rem 0"
+ {% if prefs_updated %}
+ open
+ {% endif %}
+ >
+ <summary>User preferences</summary>
+ <form method="POST" class="prefs">
+ {{csrf_token()}}
+ <label class="form-check">
+ <input
+ type="checkbox"
+ name="notify-self"
+ id="notify-self"
+ {{ "checked" if current_user.notify_self }} />
+ Notify me of my own activity
+ </label>
+ <button class="btn btn-primary" type="submit">
+ Apply {{icon("caret-right")}}
+ </button>
+ </form>
+ </details>
{% endif %}
{% if len(trackers) > 0 %}
<h3>{{ tracker_list_msg }}</h3>
M todosrht/tickets.py => todosrht/tickets.py +8 -7
@@ 211,7 211,7 @@ def _change_ticket_status(ticket, resolve, resolution, reopen):
old_resolution, ticket.resolution)
def _send_comment_notifications(
- participant, ticket, event, comment, resolution):
+ participant, ticket, event, comment, resolution, from_email):
"""
Notify users subscribed to the ticket or tracker.
Returns a list of notified users.
@@ 230,7 230,7 @@ def _send_comment_notifications(
for subscriber, subscription in subscriptions.items():
_create_event_notification(subscriber, event)
- if subscriber != participant:
+ if (participant.notify_self and not from_email) or subscriber != participant:
_send_comment_notification(
subscription, ticket, participant, event, comment, resolution)
@@ 295,7 295,8 @@ def _handle_mentions(ticket, submitter, text, notified_users, comment=None):
def add_comment(submitter, ticket,
- text=None, resolve=False, resolution=None, reopen=False):
+ text=None, resolve=False, resolution=None, reopen=False,
+ from_email=False):
"""
Comment on a ticket, optionally resolve or reopen the ticket.
"""
@@ 311,7 312,7 @@ def add_comment(submitter, ticket,
return None
event = _create_comment_event(ticket, submitter, comment, status_change)
notified_participants = _send_comment_notifications(
- submitter, ticket, event, comment, resolution)
+ submitter, ticket, event, comment, resolution, from_email)
if comment and comment.text:
_handle_mentions(
@@ 406,7 407,7 @@ def assign(ticket, assignee, assigner):
assigner_participant = get_participant_for_user(assigner)
subscription = get_or_create_subscription(ticket, assignee_participant)
- if assigner != assignee:
+ if assigner.notify_self or assigner != assignee:
notify_assignee(subscription, ticket, assigner, assignee)
event = Event()
@@ 500,8 501,8 @@ def submit_ticket(tracker, submitter, title, description,
# Send notifications
for sub in all_subscriptions.values():
_create_event_notification(sub.participant, event)
- # Notify submitter for tickets created by email
- if from_email or sub.participant != submitter:
+ # Always notify submitter for tickets created by email
+ if from_email or submitter.notify_self or sub.participant != submitter:
_send_new_ticket_notification(sub, ticket, from_email_id)
_handle_mentions(
M todosrht/types/__init__.py => todosrht/types/__init__.py +2 -1
@@ 1,9 1,10 @@
from srht.database import Base
from srht.oauth import ExternalUserMixin
from srht.oauth import ExternalOAuthTokenMixin
+import sqlalchemy as sa
class User(Base, ExternalUserMixin):
- pass
+ notify_self = sa.Column(sa.Boolean, nullable=False, server_default="FALSE")
class OAuthToken(Base, ExternalOAuthTokenMixin):
pass
M todosrht/types/participant.py => todosrht/types/participant.py +7 -0
@@ 52,6 52,13 @@ class Participant(Base):
return self.external_id
assert False
+ @property
+ def notify_self(self):
+ if self.participant_type == ParticipantType.user:
+ return self.user.notify_self
+ else:
+ return False
+
def __str__(self):
return self.name