From 7cf785e51cce3f77505c73ead6107dc9e3c73f93 Mon Sep 17 00:00:00 2001 From: Deimos Date: Wed, 26 Sep 2018 00:22:25 -0600 Subject: [PATCH] Limit Exemplary comment label to one/day/user This is a bit of a hacky way of doing it in a few aspects and definitely still needs some work, but it's a reasonable start. Specifically, a major miss is that there's no way to remove an Exemplary label unless you currently have one available. This should definitely be fixed, but I'm not sure what might be the best approach yet. --- ...ser_track_last_usage_of_exemplary_label.py | 56 +++++++++++++++++++ tildes/scss/modules/_btn.scss | 3 +- tildes/scss/modules/_comment.scss | 7 ++- .../init/triggers/comment_labels/users.sql | 19 +++++++ .../js/behaviors/comment-label-button.js | 12 +++- tildes/tildes/models/comment/comment_label.py | 8 ++- tildes/tildes/models/user/user.py | 15 ++++- .../tildes/templates/macros/comments.jinja2 | 5 +- tildes/tildes/views/api/web/comment.py | 4 ++ 9 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 tildes/alembic/versions/8e54f422541c_user_track_last_usage_of_exemplary_label.py create mode 100644 tildes/sql/init/triggers/comment_labels/users.sql diff --git a/tildes/alembic/versions/8e54f422541c_user_track_last_usage_of_exemplary_label.py b/tildes/alembic/versions/8e54f422541c_user_track_last_usage_of_exemplary_label.py new file mode 100644 index 0000000..4bd5c18 --- /dev/null +++ b/tildes/alembic/versions/8e54f422541c_user_track_last_usage_of_exemplary_label.py @@ -0,0 +1,56 @@ +"""User: track last usage of exemplary label + +Revision ID: 8e54f422541c +Revises: 5cd2db18b722 +Create Date: 2018-09-26 00:22:02.728425 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "8e54f422541c" +down_revision = "5cd2db18b722" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "users", + sa.Column( + "last_exemplary_label_time", sa.TIMESTAMP(timezone=True), nullable=True + ), + ) + + op.execute( + """ + CREATE OR REPLACE FUNCTION update_user_last_exemplary_label_time() RETURNS TRIGGER AS $$ + BEGIN + UPDATE users + SET last_exemplary_label_time = NOW() + WHERE user_id = NEW.user_id; + + RETURN NULL; + END + $$ LANGUAGE plpgsql; + """ + ) + + op.execute( + """ + CREATE TRIGGER update_user_last_exemplary_label_time + AFTER INSERT ON comment_labels + FOR EACH ROW + WHEN (NEW.label = 'EXEMPLARY') + EXECUTE PROCEDURE update_user_last_exemplary_label_time(); + """ + ) + + +def downgrade(): + op.execute("DROP TRIGGER update_user_last_exemplary_label_time ON comment_labels") + op.execute("DROP FUNCTION update_user_last_exemplary_label_time()") + + op.drop_column("users", "last_exemplary_label_time") diff --git a/tildes/scss/modules/_btn.scss b/tildes/scss/modules/_btn.scss index 9acb696..b91b5f0 100644 --- a/tildes/scss/modules/_btn.scss +++ b/tildes/scss/modules/_btn.scss @@ -69,7 +69,8 @@ .btn-comment-label { display: inline-flex; align-items: center; - margin: 0.4rem; + margin: 0 0.4rem; + padding: 0.2rem; font-size: 0.6rem; font-weight: bold; diff --git a/tildes/scss/modules/_comment.scss b/tildes/scss/modules/_comment.scss index d65cbe6..802e8c6 100644 --- a/tildes/scss/modules/_comment.scss +++ b/tildes/scss/modules/_comment.scss @@ -102,7 +102,6 @@ margin: 0; padding: 0 1rem; justify-content: space-between; - align-items: center; @media (min-width: $size-md) { justify-content: left; @@ -113,6 +112,12 @@ font-size: 0.6rem; } +.comment-label-note { + font-size: 0.5rem; + font-style: italic; + text-align: center; +} + .comment-text { padding: 0.2rem; padding-left: 0.4rem; diff --git a/tildes/sql/init/triggers/comment_labels/users.sql b/tildes/sql/init/triggers/comment_labels/users.sql new file mode 100644 index 0000000..907256d --- /dev/null +++ b/tildes/sql/init/triggers/comment_labels/users.sql @@ -0,0 +1,19 @@ +-- Copyright (c) 2018 Tildes contributors +-- SPDX-License-Identifier: AGPL-3.0-or-later + +CREATE OR REPLACE FUNCTION update_user_last_exemplary_label_time() RETURNS TRIGGER AS $$ +BEGIN + UPDATE users + SET last_exemplary_label_time = NOW() + WHERE user_id = NEW.user_id; + + RETURN NULL; +END +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER update_user_last_exemplary_label_time + AFTER INSERT ON comment_labels + FOR EACH ROW + WHEN (NEW.label = 'EXEMPLARY') + EXECUTE PROCEDURE update_user_last_exemplary_label_time(); diff --git a/tildes/static/js/behaviors/comment-label-button.js b/tildes/static/js/behaviors/comment-label-button.js index 5cfd683..771fb40 100644 --- a/tildes/static/js/behaviors/comment-label-button.js +++ b/tildes/static/js/behaviors/comment-label-button.js @@ -55,7 +55,17 @@ $.onmount('[data-js-comment-label-button]', function() { } $(label).on('after.success.ic', function(evt) { - Tildes.addUserLabel(commentID, evt.target.textContent); + var labelName = evt.target.textContent; + Tildes.addUserLabel(commentID, labelName); + + // if the applied label was Exemplary, remove the button from the + // template since they can't use it again anyway + if (labelName === "exemplary") { + var exemplaryButton = labeltemplate.content.querySelector('.btn-comment-label-exemplary'); + if (exemplaryButton) { + exemplaryButton.parentElement.remove(); + } + } }); } diff --git a/tildes/tildes/models/comment/comment_label.py b/tildes/tildes/models/comment/comment_label.py index 451419e..4ac6208 100644 --- a/tildes/tildes/models/comment/comment_label.py +++ b/tildes/tildes/models/comment/comment_label.py @@ -18,7 +18,13 @@ from .comment import Comment class CommentLabel(DatabaseModel): - """Model for the labels attached to comments by users.""" + """Model for the labels attached to comments by users. + + Trigger behavior: + Outgoing: + - Inserting a row for an exemplary label will set last_exemplary_label_time for + the relevant user. + """ __tablename__ = "comment_labels" diff --git a/tildes/tildes/models/user/user.py b/tildes/tildes/models/user/user.py index 83cf8e4..e7db77e 100644 --- a/tildes/tildes/models/user/user.py +++ b/tildes/tildes/models/user/user.py @@ -32,7 +32,7 @@ from sqlalchemy.orm import deferred from sqlalchemy.sql.expression import text from sqlalchemy_utils import Ltree -from tildes.enums import TopicSortOption +from tildes.enums import CommentLabelOption, TopicSortOption from tildes.lib.database import ArrayOfLtree, CIText from tildes.lib.datetime import utc_now from tildes.lib.hash import hash_string, is_match_for_hash @@ -49,6 +49,8 @@ class User(DatabaseModel): deletions, and updates to is_unread in comment_notifications. - num_unread_messages will be incremented and decremented by insertions, deletions, and updates to unread_user_ids in message_conversations. + - last_exemplary_label_time will be set when a row for an exemplary label is + inserted into comment_labels. """ schema_class = UserSchema @@ -102,6 +104,7 @@ class User(DatabaseModel): "filtered_topic_tags", ArrayOfLtree, nullable=False, server_default="{}" ) comment_label_weight: Optional[float] = Column(REAL) + last_exemplary_label_time: Optional[datetime] = Column(TIMESTAMP(timezone=True)) @hybrid_property def filtered_topic_tags(self) -> List[str]: @@ -238,3 +241,13 @@ class User(DatabaseModel): def is_admin(self) -> bool: """Return whether the user has admin permissions.""" return "admin" in self.auth_principals + + def is_label_available(self, label: CommentLabelOption) -> bool: + """Return whether the user has a particular label available.""" + if label == CommentLabelOption.EXEMPLARY: + if not self.last_exemplary_label_time: + return True + + return utc_now() - self.last_exemplary_label_time > timedelta(hours=24) + + return True diff --git a/tildes/tildes/templates/macros/comments.jinja2 b/tildes/tildes/templates/macros/comments.jinja2 index 050b1da..c1f08f2 100644 --- a/tildes/tildes/templates/macros/comments.jinja2 +++ b/tildes/tildes/templates/macros/comments.jinja2 @@ -254,13 +254,16 @@ {% macro comment_label_options_template(options) %}