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) %}