From 6f34f7cef6957e6a33359bf7283c8b63ca071c5c Mon Sep 17 00:00:00 2001 From: Owocki Date: Wed, 6 Nov 2019 13:35:45 -0700 Subject: [PATCH 01/18] quest update + low bounty notif update --- app/dashboard/helpers.py | 2 +- app/quests/helpers.py | 4 +++- app/quests/templates/quests/types/quiz_style.html | 8 +++++++- app/quests/views.py | 4 +++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/dashboard/helpers.py b/app/dashboard/helpers.py index 44e2c7d9c81..35c2cf386e1 100644 --- a/app/dashboard/helpers.py +++ b/app/dashboard/helpers.py @@ -900,7 +900,7 @@ def process_bounty_changes(old_bounty, new_bounty): # Send an Email if this is a LowBall bounty try: if(not old_bounty or old_bounty.value_in_usdt != new_bounty.value_in_usdt): - if is_lowball_bounty(new_bounty.value_in_usdt): + if is_lowball_bounty(new_bounty.value_in_usdt) and new_bounty.network == 'mainnet': notify_of_lowball_bounty(new_bounty) except Exception as e: logger.error(f'{e} during check for Lowball Bounty') diff --git a/app/quests/helpers.py b/app/quests/helpers.py index e025128d080..bb76dd0826a 100644 --- a/app/quests/helpers.py +++ b/app/quests/helpers.py @@ -95,12 +95,14 @@ def get_base_quest_view_params(user, quest): """ profile = user.profile if user.is_authenticated else None attempts = quest.attempts.filter(profile=profile) if profile else QuestAttempt.objects.none() - + is_owner = quest.creator.pk == user.profile.pk if user.is_authenticated else False params = { 'quest': quest, 'hide_col': True, 'attempt_count': attempts.count() + 1, 'success_count': attempts.filter(success=True).count(), + 'is_owner': is_owner, + 'is_owner_or_staff': is_owner or user.is_staff, 'body_class': 'quest_battle', 'title': "Play the *" + quest.title + (f"* Gitcoin Quest and win a *{quest.kudos_reward.humanized_name}* Kudos" if quest.kudos_reward else ""), 'avatar_url': quest.avatar_url_png, diff --git a/app/quests/templates/quests/types/quiz_style.html b/app/quests/templates/quests/types/quiz_style.html index 85ae68b9d83..c9d8546ff28 100644 --- a/app/quests/templates/quests/types/quiz_style.html +++ b/app/quests/templates/quests/types/quiz_style.html @@ -22,10 +22,16 @@ -{% if not hidden and is_staff %} +{% if not hidden and is_owner_or_staff %}
+ {% if is_staff %} Staff only | {% trans "Quest Admin" %} + {% endif %} + {% if is_staff or is_owner %} + {% if not is_staff %} Owner Only {% endif %} | + {% trans "Edit Quest" %} + {% endif %}
{% endif %} {% endfor %} -
- -
- -
- {% include 'svgs/arrow-down.svg' %} -
-
-
{% csrf_token %} From 997082a52fd3f7006d9e9c582809f1179a993c7b Mon Sep 17 00:00:00 2001 From: Owocki Date: Wed, 6 Nov 2019 17:25:05 -0700 Subject: [PATCH 03/18] quest feedback --- app/app/urls.py | 14 +++++++++--- app/assets/v2/js/pages/quests.helpers.js | 18 +++++++++++++++ app/marketing/mails.py | 22 +++++++++++++++++++ app/quests/models.py | 5 +++++ .../templates/quests/types/quiz_style.html | 6 +++++ app/quests/views.py | 18 +++++++++++---- 6 files changed, 76 insertions(+), 7 deletions(-) diff --git a/app/app/urls.py b/app/app/urls.py index efe5acdc85e..f872d0d29cf 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -168,6 +168,7 @@ # quests re_path(r'^quests/?$', quests.views.index, name='quests_index'), re_path(r'^quests/next?$', quests.views.next_quest, name='next_quest'), + re_path(r'^quests/(?P\d+)/feedback', quests.views.feedback, name='quest_feedback'), re_path(r'^quests/(?P\d+)/(?P\w*)', quests.views.details, name='quest_details'), re_path(r'^quests/new/?', quests.views.editquest, name='newquest'), re_path(r'^quests/edit/(?P\d+)/?', quests.views.editquest, name='editquest'), @@ -187,7 +188,11 @@ path('hackathon/onboard//', dashboard.views.hackathon_onboard, name='hackathon_onboard'), path('hackathon/projects//', dashboard.views.hackathon_projects, name='hackathon_projects'), path('modal/new_project//', dashboard.views.hackathon_get_project, name='hackathon_get_project'), - path('modal/new_project///', dashboard.views.hackathon_get_project, name='hackathon_edit_project'), + path( + 'modal/new_project///', + dashboard.views.hackathon_get_project, + name='hackathon_edit_project' + ), path('modal/save_project/', dashboard.views.hackathon_save_project, name='hackathon_save_project'), re_path(r'^hackathon/?$/?', dashboard.views.hackathon, name='hackathon_idx'), re_path(r'^hackathon/(.*)?$', dashboard.views.hackathon, name='hackathon_idx2'), @@ -620,8 +625,11 @@ ] urlpatterns += [ - re_path(r'^([a-z|A-Z|0-9|\.](?:[a-z\d]|-(?=[a-z\d]))+)/([a-z|A-Z|0-9|\.]+)/?$', dashboard.views.profile, name='profile_min'), - + re_path( + r'^([a-z|A-Z|0-9|\.](?:[a-z\d]|-(?=[a-z\d]))+)/([a-z|A-Z|0-9|\.]+)/?$', + dashboard.views.profile, + name='profile_min' + ), re_path(r'^([a-z|A-Z|0-9|\.](?:[a-z\d]|-(?=[a-z\d]))+)/?$', dashboard.views.profile, name='profile_min'), ] diff --git a/app/assets/v2/js/pages/quests.helpers.js b/app/assets/v2/js/pages/quests.helpers.js index 16ed78897e7..8de7119d585 100644 --- a/app/assets/v2/js/pages/quests.helpers.js +++ b/app/assets/v2/js/pages/quests.helpers.js @@ -194,6 +194,24 @@ $(document).ready(function() { }); + $('.give_feedback').on('click', async function(e) { + e.preventDefault(); + var feedback = prompt('Please enter your feedback for the quest author.', 'Is question #3 wrong? I tried everyhing!'); + + if (!feedback) { + return; + } + var params = { + 'feedback': feedback + }; + var url = document.quest_feedback_url; + + $.post(url, params, function(response) { + _alert('Feedback sent', 'success'); + }); + }); + + if (document.quest) { start_quest(); } diff --git a/app/marketing/mails.py b/app/marketing/mails.py index 518c6949c03..baea276edf5 100644 --- a/app/marketing/mails.py +++ b/app/marketing/mails.py @@ -429,6 +429,28 @@ def new_grant_admin(grant): translation.activate(cur_language) +def send_user_feedback(quest, feedback, user): + to_email = quest.creator.email + from_email = user.email + cur_language = translation.get_language() + try: + setup_lang(to_email) + subject = f"New Gitcoin Quest Feedback: {quest.title}" + body_str = f"quest: {quest.title}\nurl: {quest.url}\nedit: {quest.edit_url}\n\n> {feedback}\n\nfrom: {user.email} ( {user.profile.url} )" + body = f"{body_str}" + if not should_suppress_notification_email(to_email, 'quest'): + send_mail( + from_email, + to_email, + subject, + body, + from_name=f"@{user.profile.handle} on gitcoin.co", + categories=['admin', func_name()], + ) + finally: + translation.activate(cur_language) + + def new_quest_request(quest, is_edit): to_email = settings.PERSONAL_CONTACT_EMAIL from_email = settings.SERVER_EMAIL diff --git a/app/quests/models.py b/app/quests/models.py index c51cdb4bdaf..cc9f425712d 100644 --- a/app/quests/models.py +++ b/app/quests/models.py @@ -70,6 +70,11 @@ def edit_url(self): from django.conf import settings return settings.BASE_URL + f"quests/edit/{self.pk}" + @property + def feedback_url(self): + from django.conf import settings + return settings.BASE_URL + f"quests/{self.pk}/feedback" + @property def est_read_time_mins(self): return self.game_schema.get('est_read_time_mins', 10) diff --git a/app/quests/templates/quests/types/quiz_style.html b/app/quests/templates/quests/types/quiz_style.html index c9d8546ff28..b40fd056866 100644 --- a/app/quests/templates/quests/types/quiz_style.html +++ b/app/quests/templates/quests/types/quiz_style.html @@ -81,6 +81,10 @@

@{% if user and user.username %}{{ user.username }}{% else %}me{% endif %}
@@ -179,7 +179,7 @@
{{ project.name }}
From 3e40ebdf8926c9dd0fb7106a69e5e1ed671397e7 Mon Sep 17 00:00:00 2001 From: Owocki Date: Thu, 7 Nov 2019 15:58:39 -0700 Subject: [PATCH 08/18] adds the ability to give quiz feedback --- app/assets/v2/css/quests.css | 11 ++++++ app/assets/v2/js/pages/quests.helpers.js | 12 +++---- .../v2/js/pages/quests.quest.quiz_style.js | 9 +++-- app/quests/admin.py | 26 ++++++++++++-- app/quests/models.py | 35 ++++++++++++++++++- .../templates/quests/types/quiz_style.html | 10 +++--- app/quests/views.py | 15 ++++++-- 7 files changed, 100 insertions(+), 18 deletions(-) diff --git a/app/assets/v2/css/quests.css b/app/assets/v2/css/quests.css index d199b42079f..d82502c9b40 100644 --- a/app/assets/v2/css/quests.css +++ b/app/assets/v2/css/quests.css @@ -573,6 +573,7 @@ body.quest_battle .bottom_notification{ bottom: 0px; right: 10px; } + .bl{ position: absolute; bottom: 20px; @@ -1054,6 +1055,16 @@ body.green .tl{ body.back11.quest_battle{ background-size: 100% !important; } + .br a{ + display: inline-block; + padding: 5px 5px; + margin: 4px 4px; + background-color: rgba(256,256,256,0.3); + } + .br a:hover{ + background-color: rgba(256,256,256,0.7); + text-decoration: none; + } #cta_button a, a.button{ line-height: 46px; padding: 10px; diff --git a/app/assets/v2/js/pages/quests.helpers.js b/app/assets/v2/js/pages/quests.helpers.js index 0e1d571b545..0fa7f39ea67 100644 --- a/app/assets/v2/js/pages/quests.helpers.js +++ b/app/assets/v2/js/pages/quests.helpers.js @@ -205,18 +205,18 @@ $(document).ready(function() { $('.give_feedback').on('click', async function(e) { e.preventDefault(); - var feedback = prompt('Please enter your feedback for the quest author.', 'Is question #3 wrong? I tried everyhing!'); - - if (!feedback) { - return; - } + var feedback = prompt('Any comments for the quest author? (optional)', 'Is question #3 wrong? I tried everyhing!'); + var polarity = $(this).data('direction'); + var params = { + 'polarity': polarity, 'feedback': feedback }; var url = document.quest_feedback_url; $.post(url, params, function(response) { - _alert('Feedback sent', 'success'); + _alert('Thank you for your feedback on this quest.', 'success'); + $('#vote_container').remove(); }); }); diff --git a/app/assets/v2/js/pages/quests.quest.quiz_style.js b/app/assets/v2/js/pages/quests.quest.quiz_style.js index d11bcbe61b7..23cfe84c826 100644 --- a/app/assets/v2/js/pages/quests.quest.quiz_style.js +++ b/app/assets/v2/js/pages/quests.quest.quiz_style.js @@ -162,6 +162,8 @@ var advance_to_state = async function(new_state) { // -- individual transitions callbacks -- // 0 to 1 + var new_html; + if (old_state == 0 && new_state == 1) { await sleep(1000); await $('#header').html('Quest Intro'); @@ -178,7 +180,9 @@ var advance_to_state = async function(new_state) { await wait_for_typewriter(); var kudos_reward_html = "

If you're successful in this quest, you'll earn this limited edition " + document.kudos_reward['name'] + " Kudos:

+ratio: {fb['ratio']} + +stats: {fb['stats']} + +feedback: {fb['feedback']} + + + """ + return mark_safe(html) + def background_preview(self, instance): html = '' for ext in ['png', 'jpg']: @@ -54,11 +68,19 @@ class QuestAttemptAdmin(admin.ModelAdmin): ordering = ['-id'] list_display = ['created_on', '__str__'] + +class QuestFeedbackAdmin(admin.ModelAdmin): + raw_id_fields = ['quest', 'profile'] + ordering = ['-id'] + list_display = ['created_on', '__str__'] + + class QuestPointAwardAdmin(admin.ModelAdmin): raw_id_fields = ['questattempt', 'profile'] ordering = ['-id'] list_display = ['created_on', '__str__'] +admin.site.register(QuestFeedback, QuestFeedbackAdmin) admin.site.register(QuestPointAward, QuestPointAwardAdmin) admin.site.register(Quest, QuestAdmin) admin.site.register(QuestAttempt, QuestAttemptAdmin) diff --git a/app/quests/models.py b/app/quests/models.py index cc9f425712d..574f15aa4b5 100644 --- a/app/quests/models.py +++ b/app/quests/models.py @@ -59,7 +59,6 @@ def __str__(self): """Return the string representation of this obj.""" return f'{self.pk}, {self.title} (visible: {self.visible})' - @property def url(self): from django.conf import settings @@ -75,6 +74,23 @@ def feedback_url(self): from django.conf import settings return settings.BASE_URL + f"quests/{self.pk}/feedback" + @property + def feedbacks(self): + stats = {1 : 0, -1 : 0, 0 : 0} + for fb in self.feedback.all(): + stats[fb.vote] += 1 + ratio_upvotes = 0 + if self.feedback.count(): + ratio_upvotes = stats[1]/self.feedback.count() + return_me = { + 'ratio': ratio_upvotes, + 'stats': stats, + 'feedback': [] + } + for fb in self.feedback.all(): + return_me['feedback'].append(fb.comment) + return return_me + @property def est_read_time_mins(self): return self.game_schema.get('est_read_time_mins', 10) @@ -230,6 +246,7 @@ def psave_quest(sender, instance, **kwargs): instance.ui_data['attempts_count'] = instance.attempts.count() instance.ui_data['tags'] = instance.tags instance.ui_data['success_pct'] = instance.success_pct + instance.ui_data['feedbacks'] = instance.feedbacks instance.ui_data['creator'] = { 'url': instance.creator.url, 'handle': instance.creator.handle, @@ -252,6 +269,22 @@ def __str__(self): return f'{self.pk}, {self.profile.handle} => {self.quest.title} state: {self.state} success: {self.success}' +class QuestFeedback(SuperModel): + + quest = models.ForeignKey('quests.Quest', blank=True, null=True, related_name='feedback', on_delete=models.SET_NULL) + profile = models.ForeignKey( + 'dashboard.Profile', + on_delete=models.CASCADE, + related_name='quest_feedback', + ) + vote = models.IntegerField(default=1) + comment = models.TextField(default='', blank=True) + + def __str__(self): + """Return the string representation of this obj.""" + return f'{self.pk}, {self.profile.handle} => {self.quest.title} ({self.comment})' + + class QuestPointAward(SuperModel): questattempt = models.ForeignKey('quests.QuestAttempt', related_name='pointawards', on_delete=models.CASCADE) diff --git a/app/quests/templates/quests/types/quiz_style.html b/app/quests/templates/quests/types/quiz_style.html index b40fd056866..54aa1acab45 100644 --- a/app/quests/templates/quests/types/quiz_style.html +++ b/app/quests/templates/quests/types/quiz_style.html @@ -81,10 +81,6 @@

@{% if user and user.username %}{{ user.username }}{% else %}me{% endif %}
-
From bf5575c331a81e5fcf313070114fb9f68f4a9bb9 Mon Sep 17 00:00:00 2001 From: Owocki Date: Mon, 11 Nov 2019 11:36:50 -0700 Subject: [PATCH 17/18] wonky copy --- app/retail/templates/cookielaw/banner.html | 4 ++-- app/retail/templates/emails/share_bounty_email.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/retail/templates/cookielaw/banner.html b/app/retail/templates/cookielaw/banner.html index 067dbb7f978..59af5cd5453 100644 --- a/app/retail/templates/cookielaw/banner.html +++ b/app/retail/templates/cookielaw/banner.html @@ -14,8 +14,8 @@ diff --git a/app/retail/templates/emails/share_bounty_email.html b/app/retail/templates/emails/share_bounty_email.html index 4ac71cc8bd6..9a91cc86121 100644 --- a/app/retail/templates/emails/share_bounty_email.html +++ b/app/retail/templates/emails/share_bounty_email.html @@ -32,7 +32,7 @@

@{{from_profile.handle}} {% trans "invited you to work on a bounty" %}

< {% if kudos_invite %}
-

{% trans "... you can get a special Kudos too" %}

+

If you accept the invite... you can get a special Kudos from @{{from_profile.handle}} for working on this bounty!



From c43a47c22b93d0e446d34aebde5b2eb5765d2417 Mon Sep 17 00:00:00 2001 From: Owocki Date: Mon, 11 Nov 2019 11:37:40 -0700 Subject: [PATCH 18/18] reliablity rating --- app/dashboard/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 4c97fde8d5b..518713591eb 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -2834,7 +2834,7 @@ def calc_reliability_ranking(self): #calculate base rating num_earnings = self.earnings.count() + self.sent_earnings.count() - if num_earnings == 0: + if num_earnings < 2: return "Unproven" if num_earnings > high_threshold: