From 79979ca74fd682235589ee482fddaee93870d701 Mon Sep 17 00:00:00 2001 From: owocki Date: Thu, 27 Aug 2020 08:32:58 -0600 Subject: [PATCH 01/33] updates grants round 7 start, to allow for deploly + QA --- app/grants/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/grants/views.py b/app/grants/views.py index 40f784eff1b..aba8c28180f 100644 --- a/app/grants/views.py +++ b/app/grants/views.py @@ -88,7 +88,7 @@ last_round_end = timezone.datetime(2020, 4, 7, 12, 0) # TODO, also update grants.clr:CLR_START_DATE, PREV_CLR_START_DATE, PREV_CLR_END_DATE next_round_start = timezone.datetime(2020, 6, 15, 12, 0) -next_round_start = timezone.datetime(2020, 9, 14) +next_round_start = timezone.datetime(2020, 9, 15, 9, 0) after_that_next_round_begin = timezone.datetime(2020, 9, 14, 12, 0) round_end = timezone.datetime(2020, 7, 3, 16, 0) #tz=utc, not mst round_types = ['media', 'tech', 'change'] From 2071180c893002340b2a544e94a6e959b0199ef5 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Thu, 27 Aug 2020 11:53:34 -0300 Subject: [PATCH 02/33] sentry user_card fixes --- app/assets/v2/js/user_card.js | 3 +++ .../templates/dashboard/hackathon/project_page.html | 1 - app/dashboard/templates/profiles/profile.html | 1 - app/dashboard/templates/profiles/tribes-vue.html | 1 - app/dashboard/templates/shared/search.html | 2 +- app/retail/templates/leaderboard.html | 9 ++++----- app/retail/templates/shared/footer_scripts.html | 1 + app/retail/templates/shared/footer_scripts_lite.html | 1 + app/townsquare/templates/townsquare/index.html | 1 - 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/assets/v2/js/user_card.js b/app/assets/v2/js/user_card.js index 4b3e43f8d80..8ed048e8557 100644 --- a/app/assets/v2/js/user_card.js +++ b/app/assets/v2/js/user_card.js @@ -243,6 +243,9 @@ const addFollowAction = () => { }; function openContributorPopOver(contributor, element) { + if (!contributor) { + return; + } const contributorURL = `/api/v0.1/user_card/${contributor}`; diff --git a/app/dashboard/templates/dashboard/hackathon/project_page.html b/app/dashboard/templates/dashboard/hackathon/project_page.html index 4f0c8aee32d..64d7915f4b0 100644 --- a/app/dashboard/templates/dashboard/hackathon/project_page.html +++ b/app/dashboard/templates/dashboard/hackathon/project_page.html @@ -89,7 +89,6 @@ {{currentProfile|json_script:"current-profile"}} - {% include 'shared/activity_scripts.html' %} diff --git a/app/dashboard/templates/profiles/profile.html b/app/dashboard/templates/profiles/profile.html index cc2b8ce636b..1387827cee9 100644 --- a/app/dashboard/templates/profiles/profile.html +++ b/app/dashboard/templates/profiles/profile.html @@ -113,7 +113,6 @@ - diff --git a/app/dashboard/templates/profiles/tribes-vue.html b/app/dashboard/templates/profiles/tribes-vue.html index dc7be95ecd2..9a9c91b5fa1 100644 --- a/app/dashboard/templates/profiles/tribes-vue.html +++ b/app/dashboard/templates/profiles/tribes-vue.html @@ -1552,7 +1552,6 @@

{% trans "Top id {% include 'shared/tip_dependancies.html' %} - - + {% if user.is_authenticated %} diff --git a/app/retail/templates/shared/footer_scripts_lite.html b/app/retail/templates/shared/footer_scripts_lite.html index fa317ae7fae..4169ba97300 100644 --- a/app/retail/templates/shared/footer_scripts_lite.html +++ b/app/retail/templates/shared/footer_scripts_lite.html @@ -62,6 +62,7 @@ + diff --git a/app/townsquare/templates/townsquare/index.html b/app/townsquare/templates/townsquare/index.html index 85c1eb38d85..ebb73a060fd 100644 --- a/app/townsquare/templates/townsquare/index.html +++ b/app/townsquare/templates/townsquare/index.html @@ -287,6 +287,5 @@

We’re excited you’re here! Let’s ge - From 4e490ff6916019c0479113a48bc9c508d72f6a1e Mon Sep 17 00:00:00 2001 From: owocki Date: Thu, 27 Aug 2020 09:12:21 -0600 Subject: [PATCH 03/33] hack list --- app/dashboard/templates/dashboard/hackathon/hackathons.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/templates/dashboard/hackathon/hackathons.html b/app/dashboard/templates/dashboard/hackathon/hackathons.html index 4aefaf1a005..43cb734283d 100644 --- a/app/dashboard/templates/dashboard/hackathon/hackathons.html +++ b/app/dashboard/templates/dashboard/hackathon/hackathons.html @@ -155,7 +155,7 @@

{{ event.summary }}

- {% if event.sponsor_profiles %} + {% if event.sponsor_profiles|length %}
Sponsored by {% for sponsor in event.sponsor_profiles %} From 4fb7da9f42e1695d3579feec885e3dcb032774a2 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Thu, 27 Aug 2020 12:19:39 -0300 Subject: [PATCH 04/33] card fixes --- app/retail/templates/leaderboard.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/retail/templates/leaderboard.html b/app/retail/templates/leaderboard.html index fa4ca2e2a45..597355b8439 100644 --- a/app/retail/templates/leaderboard.html +++ b/app/retail/templates/leaderboard.html @@ -28,7 +28,7 @@ {% for podium_item in podium_items %}
{% if not is_linked_to_profile %} - + {% endif %} From 9357b9b5bc7dae94f91bd1dd9c60911dfaedf2e8 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Thu, 27 Aug 2020 12:35:33 -0300 Subject: [PATCH 05/33] profile project url --- app/dashboard/templates/profiles/tab_hackathons.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/dashboard/templates/profiles/tab_hackathons.html b/app/dashboard/templates/profiles/tab_hackathons.html index 7abc77312ed..5a8922eef5b 100644 --- a/app/dashboard/templates/profiles/tab_hackathons.html +++ b/app/dashboard/templates/profiles/tab_hackathons.html @@ -66,8 +66,7 @@ {% endif %}
-
{{ project.name }}
- Project Home +
{{ project.name }}
Project Summary

From ace245686b5e7a4376eb31b65fe639efdc9492fe Mon Sep 17 00:00:00 2001 From: owocki Date: Thu, 27 Aug 2020 10:27:22 -0600 Subject: [PATCH 06/33] fix for https://gitcoincore.slack.com/archives/CAXQ7PT60/p1598454051039600 --- 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 ab814d0dedd..11d412e07b5 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -857,7 +857,7 @@ def value_in_usdt_then(self): def value_in_usdt_at_time(self, at_time): decimals = 10 ** 18 - if self.token_name == 'USDT': + if self.token_name in ['USDT', 'USDC']: return float(self.value_in_token / 10 ** 6) if self.token_name in settings.STABLE_COINS: return float(self.value_in_token / 10 ** 18) From 46ffd9cf871176e4d454dfc6aac5fd9890778a11 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Thu, 27 Aug 2020 19:13:52 -0300 Subject: [PATCH 07/33] fix fees data not saving --- app/assets/v2/js/pages/new_bounty.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/v2/js/pages/new_bounty.js b/app/assets/v2/js/pages/new_bounty.js index 3b539830460..4fa6b3c5d2b 100644 --- a/app/assets/v2/js/pages/new_bounty.js +++ b/app/assets/v2/js/pages/new_bounty.js @@ -358,7 +358,7 @@ Vue.mixin({ if (errors) { _alert({ message: gettext('Unable to pay bounty fee. Please try again.') }, 'error'); } else { - + vm.form.feeTxId = txnHash; saveAttestationData( txnHash, vm.totalAmount.totalFee, @@ -461,8 +461,8 @@ Vue.mixin({ 'repo_type': metadata.repo_type, 'is_featured': metadata.is_featured, 'featuring_date': metadata.featuring_date, - 'fee_amount': 0, - 'fee_tx_id': null, + 'fee_amount': vm.totalAmount.totalFee, + 'fee_tx_id': vm.form.feeTxId, 'coupon_code': vm.form.couponCode, 'privacy_preferences': JSON.stringify({ show_email_publicly: vm.form.showEmailPublicly @@ -662,6 +662,7 @@ if (document.getElementById('gc-hackathon-new-bounty')) { token: {}, terms: false, termsPrivacy: false, + feeTxId: null, couponCode: document.coupon_code } }; From 982ead66d6a2c65de20da5d360248c065a18d893 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Thu, 27 Aug 2020 20:05:14 -0300 Subject: [PATCH 08/33] remove repeated call to user-cards --- app/dashboard/templates/dashboard/index-vue.html | 1 - app/dashboard/templates/dashboard/sponsors.html | 1 - app/quests/templates/quests/index.html | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/dashboard/templates/dashboard/index-vue.html b/app/dashboard/templates/dashboard/index-vue.html index 5e29f44db4d..ece938b6d87 100644 --- a/app/dashboard/templates/dashboard/index-vue.html +++ b/app/dashboard/templates/dashboard/index-vue.html @@ -856,7 +856,6 @@

[[hackathon.name]] Wall of Fame< {% include 'shared/activity_scripts.html' %} - {% include 'shared/current_profile.html' %} diff --git a/app/dashboard/templates/dashboard/sponsors.html b/app/dashboard/templates/dashboard/sponsors.html index b1df7bb47a2..4a4f701adb4 100644 --- a/app/dashboard/templates/dashboard/sponsors.html +++ b/app/dashboard/templates/dashboard/sponsors.html @@ -382,7 +382,6 @@ {% include 'shared/activity_scripts.html' %} - {% include 'shared/current_profile.html' %} diff --git a/app/quests/templates/quests/index.html b/app/quests/templates/quests/index.html index 2ae39ad776d..55b24d784f9 100644 --- a/app/quests/templates/quests/index.html +++ b/app/quests/templates/quests/index.html @@ -101,7 +101,7 @@

No Quests Found

{{quest.kudos_reward.humanized_name}} {{difficulty_level}} {{quest.ui_data.attempts_count}} - + Info @@ -575,7 +575,6 @@

Play Gitcoin Quests Now.

{% block 'scripts' %} - - + + + diff --git a/app/retail/templates/shared/footer_scripts_lite.html b/app/retail/templates/shared/footer_scripts_lite.html index 4169ba97300..9ea8fc7e943 100644 --- a/app/retail/templates/shared/footer_scripts_lite.html +++ b/app/retail/templates/shared/footer_scripts_lite.html @@ -47,7 +47,8 @@ - + + From 2e1df41c6780ff1341bd91240fd0f096df73f0a2 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Thu, 27 Aug 2020 20:24:03 -0300 Subject: [PATCH 10/33] bump walletconnect to version 1.2.1 --- app/retail/templates/shared/footer_scripts.html | 2 +- app/retail/templates/shared/footer_scripts_lite.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/retail/templates/shared/footer_scripts.html b/app/retail/templates/shared/footer_scripts.html index 2ae1ea5b884..574ec1830eb 100644 --- a/app/retail/templates/shared/footer_scripts.html +++ b/app/retail/templates/shared/footer_scripts.html @@ -50,7 +50,7 @@ - + diff --git a/app/retail/templates/shared/footer_scripts_lite.html b/app/retail/templates/shared/footer_scripts_lite.html index 9ea8fc7e943..4a5d15e5979 100644 --- a/app/retail/templates/shared/footer_scripts_lite.html +++ b/app/retail/templates/shared/footer_scripts_lite.html @@ -48,7 +48,7 @@ - + From e3f4f7d0f44d6de4ae7622c8a8d70db0eae7bcfb Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Fri, 28 Aug 2020 08:19:05 +0530 Subject: [PATCH 11/33] test bot flow --- app/dashboard/models.py | 11 ++++++++++- app/dashboard/notifications.py | 3 +++ app/dashboard/views.py | 1 + .../management/commands/insert_contributions.py | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 11d412e07b5..732a024081c 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -1087,14 +1087,23 @@ def is_notification_eligible(self, var_to_check=True): bool: Whether or not the Bounty is eligible for outbound notifications. """ + print(f'### GITCOIN BOT A2 var_to_check: {var_to_check}') + print(f'### GITCOIN BOT A2 get_natural_value: {self.get_natural_value()}') + a = self.get_natural_value() < 0.0001 + print(f'### GITCOIN BOT A2 get_natural_value < 0.0001: {a}') + print(f'### GITCOIN BOT A2 network {self.network}') + print(f'### GITCOIN BOT A2 settings.DEBUG {settings.DEBUG}') + if not var_to_check or self.get_natural_value() < 0.0001 or ( self.network != settings.ENABLE_NOTIFICATIONS_ON_NETWORK): return False + print(f'### GITCOIN BOT A3') if self.network == 'mainnet' and (settings.DEBUG or settings.ENV != 'prod'): return False + print(f'### GITCOIN BOT A4') if (settings.DEBUG or settings.ENV != 'prod') and settings.GITHUB_API_USER != self.github_org_name: return False - + print(f'### GITCOIN BOT A5') return True @property diff --git a/app/dashboard/notifications.py b/app/dashboard/notifications.py index ed413aa4b28..61e5f6bb500 100644 --- a/app/dashboard/notifications.py +++ b/app/dashboard/notifications.py @@ -600,9 +600,11 @@ def maybe_market_to_github(bounty, event_name, profile_pairs=None): bool: Whether or not the Github comment was posted successfully. """ + print(f'### GITCOIN BOT A1') if not bounty.is_notification_eligible(var_to_check=settings.GITHUB_CLIENT_ID): return False + print(f'### GITCOIN BOT A6') # Define posting specific variables. comment_id = None url = bounty.github_url @@ -611,6 +613,7 @@ def maybe_market_to_github(bounty, event_name, profile_pairs=None): # Prepare the comment message string. msg = build_github_notification(bounty, event_name, profile_pairs) + print(f'### GITCOIN BOT A7 {msg}') if not msg: return False diff --git a/app/dashboard/views.py b/app/dashboard/views.py index ff5308a614b..8c65faccce0 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -5173,6 +5173,7 @@ def create_bounty_v1(request): event_name = 'new_bounty' record_bounty_activity(bounty, user, event_name) maybe_market_to_email(bounty, event_name) + print('### GITCOIN BOT A0') maybe_market_to_github(bounty, event_name) response = { diff --git a/app/grants/management/commands/insert_contributions.py b/app/grants/management/commands/insert_contributions.py index f335fb3ce2f..6a5ef75c968 100644 --- a/app/grants/management/commands/insert_contributions.py +++ b/app/grants/management/commands/insert_contributions.py @@ -63,7 +63,7 @@ def handle(self, *args, **options): subscription.grant = grant subscription.save() - subscription.successful_contribution(tx_id); + subscription.successful_contribution(tx_id) subscription.error = True #cancel subs so it doesnt try to bill again subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent" subscription.save() From 44fc05e1ae4a971d81eb72c26582b2e1a3d46025 Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Fri, 28 Aug 2020 16:03:26 +0530 Subject: [PATCH 12/33] bot trace --- app/dashboard/models.py | 5 ++++- app/dashboard/notifications.py | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 732a024081c..73ee2b4002b 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -1093,6 +1093,9 @@ def is_notification_eligible(self, var_to_check=True): print(f'### GITCOIN BOT A2 get_natural_value < 0.0001: {a}') print(f'### GITCOIN BOT A2 network {self.network}') print(f'### GITCOIN BOT A2 settings.DEBUG {settings.DEBUG}') + print(f'### GITCOIN BOT A2 settings.ENV {settings.ENV}') + print(f'### GITCOIN BOT A2 settings.GITHUB_API_USER {settings.GITHUB_API_USER}') + print(f'### GITCOIN BOT A2 self.github_org_name {self.github_org_name}') if not var_to_check or self.get_natural_value() < 0.0001 or ( self.network != settings.ENABLE_NOTIFICATIONS_ON_NETWORK): @@ -1103,7 +1106,7 @@ def is_notification_eligible(self, var_to_check=True): print(f'### GITCOIN BOT A4') if (settings.DEBUG or settings.ENV != 'prod') and settings.GITHUB_API_USER != self.github_org_name: return False - print(f'### GITCOIN BOT A5') + print(f'### GITCOIN BOT A5 - Is Eligible') return True @property diff --git a/app/dashboard/notifications.py b/app/dashboard/notifications.py index 61e5f6bb500..7d3869d6208 100644 --- a/app/dashboard/notifications.py +++ b/app/dashboard/notifications.py @@ -600,8 +600,10 @@ def maybe_market_to_github(bounty, event_name, profile_pairs=None): bool: Whether or not the Github comment was posted successfully. """ - print(f'### GITCOIN BOT A1') + c = bounty.is_notification_eligible(var_to_check=settings.GITHUB_CLIENT_ID) + print(f'### GITCOIN BOT A1 {c}') if not bounty.is_notification_eligible(var_to_check=settings.GITHUB_CLIENT_ID): + print(f'### GITCOIN BOT A6 NOT POSTING') return False print(f'### GITCOIN BOT A6') From c64bb221df40898ec629b8ebf0e2fb719fffa811 Mon Sep 17 00:00:00 2001 From: owocki Date: Fri, 28 Aug 2020 13:38:30 -0600 Subject: [PATCH 13/33] justin YOLO request https://gitcoincore.slack.com/archives/CJWQWG6J2/p1598630565055100 --- .../0016_roundupemail_hide_dynamic.py | 18 ++++++++++++++ app/marketing/models.py | 1 + app/retail/emails.py | 24 ++++++++++++++++++- .../templates/emails/bounty_roundup.html | 4 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 app/marketing/migrations/0016_roundupemail_hide_dynamic.py diff --git a/app/marketing/migrations/0016_roundupemail_hide_dynamic.py b/app/marketing/migrations/0016_roundupemail_hide_dynamic.py new file mode 100644 index 00000000000..ee250446149 --- /dev/null +++ b/app/marketing/migrations/0016_roundupemail_hide_dynamic.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2020-08-28 19:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('marketing', '0015_auto_20200626_1424'), + ] + + operations = [ + migrations.AddField( + model_name='roundupemail', + name='hide_dynamic', + field=models.BooleanField(default=False), + ), + ] diff --git a/app/marketing/models.py b/app/marketing/models.py index e7a121c8ca8..781e07cfc4c 100644 --- a/app/marketing/models.py +++ b/app/marketing/models.py @@ -401,6 +401,7 @@ class RoundupEmail(SuperModel): highlights = JSONField(default=dict, blank=True) sponsor = JSONField(default=dict, blank=True) bounties_spec = JSONField(default=dict, blank=True) + hide_dynamic = models.BooleanField(default=False) def get_absolute_url(self): return '/_administration/email/roundup' diff --git a/app/retail/emails.py b/app/retail/emails.py index 56afbf7b859..c60cca6bc2c 100644 --- a/app/retail/emails.py +++ b/app/retail/emails.py @@ -1201,6 +1201,8 @@ def render_new_bounty_roundup(to_email): from django.conf import settings from marketing.models import RoundupEmail args = RoundupEmail.objects.order_by('created_on').last() + hide_dynamic = args.hide_dynamic + subject = args.subject new_kudos_pks = args.kudos_ids.split(',') new_kudos_size_px = 150 @@ -1220,8 +1222,27 @@ def render_new_bounty_roundup(to_email):

''' - intro = args.body.replace('KUDOS_INPUT_HERE', kudos_friday) + + if hide_dynamic: + params = { + 'intro': intro, + 'intro_txt': strip_double_chars(strip_double_chars(strip_double_chars(strip_html(intro), ' '), "\n"), "\n "), + 'invert_footer': False, + 'hide_header': False, + 'subscriber': get_or_save_email_subscriber(to_email, 'internal'), + 'email_type': 'roundup', + 'email_style': email_style, + 'hide_dynamic': hide_dynamic, + 'hide_bottom_logo': True, + } + + response_html = premailer_transform(render_to_string("emails/bounty_roundup.html", params)) + response_txt = render_to_string("emails/bounty_roundup.txt", params) + + return response_html, response_txt, subject, args.from_email, args.from_name + + highlights = args.highlights sponsor = args.sponsor bounties_spec = args.bounties_spec @@ -1286,6 +1307,7 @@ def render_new_bounty_roundup(to_email): 'sponsor': sponsor, 'email_type': 'roundup', 'email_style': email_style, + 'hide_dynamic': hide_dynamic, 'hide_bottom_logo': True, } diff --git a/app/retail/templates/emails/bounty_roundup.html b/app/retail/templates/emails/bounty_roundup.html index 6a9adc08c2b..f715d765ad7 100644 --- a/app/retail/templates/emails/bounty_roundup.html +++ b/app/retail/templates/emails/bounty_roundup.html @@ -126,6 +126,7 @@
{% trans "Community Members growing open source" %}

{% endif %} + {% if not hide_dynamic %}

{% trans "💡Open Funded Issues" %}

{% trans "Grow Open Source & Make Some ETH" %}
@@ -203,5 +204,8 @@
{% trans "Recognition throughout the platform" %}

{% endif %} + + {% endif %} +
{% endblock %} From 6c7d6cc4c569a304a69cd1def0729ff26d1f3652 Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Sun, 30 Aug 2020 03:25:01 +0530 Subject: [PATCH 14/33] feat: update payoutid when txnid is replaced --- app/dashboard/admin.py | 2 +- app/dashboard/sync/eth.py | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/dashboard/admin.py b/app/dashboard/admin.py index e05f8184746..89529a43c5a 100644 --- a/app/dashboard/admin.py +++ b/app/dashboard/admin.py @@ -41,7 +41,7 @@ class BountyEventAdmin(admin.ModelAdmin): class BountyFulfillmentAdmin(admin.ModelAdmin): - raw_id_fields = ['bounty', 'profile'] + raw_id_fields = ['bounty', 'profile', 'funder_profile', 'project'] readonly_fields = ['fulfiller_github_username'] list_display = ['id', 'bounty', 'profile', 'fulfiller_github_url', 'payout_status'] search_fields = [ diff --git a/app/dashboard/sync/eth.py b/app/dashboard/sync/eth.py index 849fa2487b5..2dda4387dd7 100644 --- a/app/dashboard/sync/eth.py +++ b/app/dashboard/sync/eth.py @@ -1,16 +1,25 @@ import logging +import re from django.conf import settings from django.utils import timezone import requests +from bs4 import BeautifulSoup from dashboard.sync.helpers import record_payout_activity logger = logging.getLogger(__name__) API_KEY = settings.ETHERSCAN_API_KEY -def get_eth_txn_status(txnid, network='mainnet'): +headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0. 2272.118 Safari/537.36.'} + + +def get_eth_txn_status(fulfillment): + + txnid = fulfillment.payout_tx_id + network = fulfillment.bounty.network if fulfillment.bounty.network else None + if not txnid: return None @@ -24,7 +33,6 @@ def get_eth_txn_status(txnid, network='mainnet'): else: etherscan_url = f'https://api-rinkeby.etherscan.io/api?module=transaction&action=gettxreceiptstatus&txhash={txnid}&apikey={API_KEY}' - headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0. 2272.118 Safari/537.36.'} etherscan_response = requests.get(etherscan_url, headers=headers).json() result = etherscan_response['result'] @@ -42,6 +50,12 @@ def get_eth_txn_status(txnid, network='mainnet'): response = { 'status': 'expired' } + else: + replacedTxnId = getReplacedTX(txnid) + if replacedTxnId: + fulfillment.payout_tx_id = replacedTxnId + fulfillment.save() + response = get_eth_txn_status(fulfillment) except Exception as e: logger.error(f'error: get_eth_txn_status - {e}') @@ -51,7 +65,7 @@ def get_eth_txn_status(txnid, network='mainnet'): def sync_eth_payout(fulfillment): if fulfillment.payout_tx_id: - txn_status = get_eth_txn_status(fulfillment.payout_tx_id, fulfillment.bounty.network) + txn_status = get_eth_txn_status(fulfillment) if txn_status: if txn_status.get('status') == 'done': fulfillment.payout_status = 'done' @@ -62,3 +76,17 @@ def sync_eth_payout(fulfillment): fulfillment.payout_status = 'expired' fulfillment.save() + + +def getReplacedTX(tx): + ethurl = "https://etherscan.io/tx/" + response = requests.get(ethurl + tx, headers=headers) + soup = BeautifulSoup(response.content, "html.parser") + p = soup.find("span", "u-label u-label--sm u-label--warning rounded") + if not p: + return None + if "Replaced" in p.text: + q = soup.find(href=re.compile("/tx/0x")) + return q.text + else: + return None From 9581fdb995612685063d91f5766a269743461874 Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Mon, 31 Aug 2020 09:43:13 -0300 Subject: [PATCH 15/33] User Directory: Elastic Search Edition. (#7204) * WIP, Frank's Super Query added as a view, connected to a ViewSet and returned through the api * WIP: Elastic Search using Haystack - indexes all Profiles, mergeing the to_dict method into the search store itself, - Bounty, and HackathonProject indexers are stubbed out to work on a path for relational models * WIP urls and search view * enable auto complete for lastname, handle, first name, and persona * remove other indexers * WIP: UserDirectory - vue search app hacked into the old user search application - dynamic filters, are all set to checkbox filters presently adjusting by type of metrics is required * wip, user cards rendering * dynamic auto complete based on fields like kibana, still WIP * WIP: dynamic filtering, some style work still needed, dynamic query building is possible * numeric list filter adjusted to account for elastic type * V0 RC1 * restore old user directory * restored docker-compose, addressed review comments * remove email from the elastic search indexer * add crontab entry for elasticsearch update * haystack settings configured to retrieve the proper env variable, removing stale ui components --- app/app/context.py | 3 +- app/app/settings.py | 12 + .../search/indexes/dashboard/bounty_text.txt | 2 + .../dashboard/hackathonproject_text.txt | 2 + .../indexes/dashboard/userdirectory_text.txt | 1 + app/app/templates/search/search.html | 172 +++++ app/app/urls.py | 3 +- app/assets/v2/js/users-elastic.js | 632 ++++++++++++++++++ app/assets/v2/js/vue-components.js | 7 +- app/dashboard/models.py | 84 +++ app/dashboard/router.py | 12 +- app/dashboard/search_indexes.py | 85 +++ .../templates/dashboard/users-elastic.html | 287 ++++++++ app/dashboard/views.py | 53 +- .../templates/shared/footer_scripts.html | 2 + docker-compose.yml | 12 +- elasticsearch.yml | 3 + requirements/base.txt | 4 +- scripts/crontab | 1 + 19 files changed, 1369 insertions(+), 8 deletions(-) create mode 100644 app/app/templates/search/indexes/dashboard/bounty_text.txt create mode 100644 app/app/templates/search/indexes/dashboard/hackathonproject_text.txt create mode 100644 app/app/templates/search/indexes/dashboard/userdirectory_text.txt create mode 100644 app/app/templates/search/search.html create mode 100644 app/assets/v2/js/users-elastic.js create mode 100644 app/dashboard/search_indexes.py create mode 100644 app/dashboard/templates/dashboard/users-elastic.html create mode 100644 elasticsearch.yml diff --git a/app/app/context.py b/app/app/context.py index 936ba48acd6..cad2a752c82 100644 --- a/app/app/context.py +++ b/app/app/context.py @@ -74,7 +74,7 @@ def preprocess(request): chat_url = get_chat_url(front_end=True) chat_access_token = '' chat_id = '' - + search_url = ''; user_is_authenticated = request.user.is_authenticated profile = request.user.profile if user_is_authenticated and hasattr(request.user, 'profile') else None if user_is_authenticated and profile and profile.pk: @@ -134,6 +134,7 @@ def preprocess(request): 'MEDIA_URL': settings.MEDIA_URL, 'max_length': max_length, 'max_length_offset': max_length_offset, + 'search_url': settings.ELASTIC_SEARCH_LB_URL, 'chat_url': chat_url, 'base_url': settings.BASE_URL, 'chat_id': chat_id, diff --git a/app/app/settings.py b/app/app/settings.py index b5f1915c12b..a70c9ae8599 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -146,6 +146,7 @@ 'wiki.plugins.macros.apps.MacrosConfig', 'adminsortable2', 'debug_toolbar', + 'haystack', ] MIDDLEWARE = [ @@ -822,6 +823,17 @@ def callback(request): ELASTIC_SEARCH_URL = env('ELASTIC_SEARCH_URL', default='') +ELASTIC_SEARCH_LB_URL = env('ELASTIC_SEARCH_LB_URL', default='') + +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'haystack.backends.elasticsearch2_backend.Elasticsearch2SearchEngine', + 'URL': f"{ELASTIC_SEARCH_URL}:9200", + 'INDEX_NAME': 'haystack', + }, +} +# Update Search index in realtime (using models.db.signals) +HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' account_sid = env('TWILIO_ACCOUNT_SID', default='') auth_token = env('TWILIO_AUTH_TOKEN', default='') diff --git a/app/app/templates/search/indexes/dashboard/bounty_text.txt b/app/app/templates/search/indexes/dashboard/bounty_text.txt new file mode 100644 index 00000000000..e21f52a6d2d --- /dev/null +++ b/app/app/templates/search/indexes/dashboard/bounty_text.txt @@ -0,0 +1,2 @@ +{{ object.title }} + diff --git a/app/app/templates/search/indexes/dashboard/hackathonproject_text.txt b/app/app/templates/search/indexes/dashboard/hackathonproject_text.txt new file mode 100644 index 00000000000..37c5aa0fa10 --- /dev/null +++ b/app/app/templates/search/indexes/dashboard/hackathonproject_text.txt @@ -0,0 +1,2 @@ +{{ object.title }} +{{ object.description }} diff --git a/app/app/templates/search/indexes/dashboard/userdirectory_text.txt b/app/app/templates/search/indexes/dashboard/userdirectory_text.txt new file mode 100644 index 00000000000..1fe45381576 --- /dev/null +++ b/app/app/templates/search/indexes/dashboard/userdirectory_text.txt @@ -0,0 +1 @@ +{{ object.object }} diff --git a/app/app/templates/search/search.html b/app/app/templates/search/search.html new file mode 100644 index 00000000000..de0f53e41dc --- /dev/null +++ b/app/app/templates/search/search.html @@ -0,0 +1,172 @@ +{% comment %} + Copyright (C) 2020 Gitcoin Core + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +{% endcomment %} +{% load i18n static email_obfuscator add_url_schema avatar_tags %} + + + + + {% include 'shared/head.html' %} + {% include 'shared/cards.html' %} + + + + + + + + {% include 'shared/tag_manager_2.html' %} +
+ {% include 'shared/top_nav.html' with class='d-md-flex' %} + {% include 'home/nav.html' %} +
+ {% block content %} +

Search

+ +
+ + {{ form.as_table }} + + + + +
  + +
+ + {% if query %} +

Results

+ + {% for result in page.object_list %} +

+ {{ result.object.title }} +

+ {% empty %} +

No results found.

+ {% endfor %} + + {% if page.has_previous or page.has_next %} +
+ {% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %} + | + {% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %} +
+ {% endif %} + {% else %} + {# Show some example queries to run, maybe query syntax, something else? #} + {% endif %} +
+{% endblock %} +
+ +
+

Hacky User Directory

+ +
+ +
+ + + + + + + +
+ +
+
+
+ + + +
+
+
+
+ + + + + + + + + +
+ + +
+ + + + + +
+
+
+
+
+ + + + {% csrf_token %} + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/footer.html' %} + + + + + + + + diff --git a/app/app/urls.py b/app/app/urls.py index 3df2be4f80b..553c6abd377 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -370,6 +370,7 @@ # User Directory re_path(r'^users/?', dashboard.views.users_directory, name='users_directory'), + re_path(r'^user_directory/?', dashboard.views.users_directory_elastic, name='users_directory_elastic'), re_path(r'^tribes/explore', dashboard.views.users_directory, name='tribes_directory'), # Alpha functionality @@ -501,7 +502,6 @@ bounty_requests.views.update_bounty_request_v1, name='update_bounty_request_v1' ), - # admin views re_path(r'^_administration/?', admin.site.urls, name='admin'), path( @@ -708,6 +708,7 @@ # users url(r'^api/v0.1/user_bounties/', dashboard.views.get_user_bounties, name='get_user_bounties'), + url(r'^api/v0.1/users_csv/', dashboard.views.output_users_to_csv, name='users_csv'), url(r'^api/v0.1/bounty_mentor/', dashboard.views.bounty_mentor, name='bounty_mentor'), url(r'^api/v0.1/users_fetch/', dashboard.views.users_fetch, name='users_fetch'), diff --git a/app/assets/v2/js/users-elastic.js b/app/assets/v2/js/users-elastic.js new file mode 100644 index 00000000000..633d1dfb0ca --- /dev/null +++ b/app/assets/v2/js/users-elastic.js @@ -0,0 +1,632 @@ +let users = []; +let usersPage = 1; +let usersNumPages = ''; +let usersHasNext = false; +let numUsers = ''; +let hackathonId = document.hasOwnProperty('hackathon_id') ? document.hackathon_id : ''; +// let funderBounties = []; + +Vue.mixin({ + methods: { + messageUser: function(handle) { + let vm = this; + const url = handle ? `${vm.chatURL}/hackathons/messages/@${handle}` : `${vm.chatURL}/`; + + chatWindow = window.open(url, 'Loading', 'top=0,left=0,width=400,height=600,status=no,toolbar=no,location=no,menubar=no,titlebar=no'); + }, + + fetchUsers: function(newPage) { + let vm = this; + + vm.isLoading = true; + vm.noResults = false; + + if (newPage) { + vm.usersPage = newPage; + } + vm.params.page = vm.usersPage; + if (hackathonId) { + vm.params.hackathon = hackathonId; + } + if (vm.searchTerm) { + vm.params.search = vm.searchTerm; + } else { + delete vm.params['search']; + } + + if (vm.hideFilterButton) { + vm.params.persona = 'tribe'; + } + + if (vm.params.persona === 'tribe') { + // remove filters which do not apply for tribes directory + delete vm.params['rating']; + delete vm.params['organisation']; + delete vm.params['skills']; + } + + if (vm.tribeFilter) { + vm.params.tribe = vm.tribeFilter; + } + + + let searchParams = new URLSearchParams(vm.params); + + let apiUrlUsers = `/api/v0.1/users_fetch/?${searchParams.toString()}`; + + if (vm.hideFilterButton) { + apiUrlUsers += '&type=explore_tribes'; + } + + var getUsers = fetchData(apiUrlUsers, 'GET'); + + $.when(getUsers).then(function(response) { + + response.data.forEach(function(item) { + vm.users.push(item); + }); + + vm.usersNumPages = response.num_pages; + vm.usersHasNext = response.has_next; + vm.numUsers = response.count; + vm.showBanner = response.show_banner; + vm.persona = response.persona; + vm.rating = response.rating; + if (vm.usersHasNext) { + vm.usersPage = ++vm.usersPage; + + } else { + vm.usersPage = 1; + } + + if (vm.users.length) { + vm.noResults = false; + } else { + vm.noResults = true; + } + vm.isLoading = false; + }); + }, + searchUsers: function() { + let vm = this; + + vm.users = []; + + vm.fetchUsers(1); + + }, + bottomVisible: function() { + let vm = this; + + const scrollY = window.scrollY; + const visible = document.documentElement.clientHeight; + const pageHeight = document.documentElement.scrollHeight - 500; + const bottomOfPage = visible + scrollY >= pageHeight; + + if (bottomOfPage || pageHeight < visible) { + if (vm.usersHasNext) { + vm.fetchUsers(); + vm.usersHasNext = false; + } + } + }, + fetchBounties: function() { + let vm = this; + + // fetch bounties + let apiUrlBounties = '/api/v0.1/user_bounties/'; + + let getBounties = fetchData(apiUrlBounties, 'GET'); + + $.when(getBounties).then((response) => { + vm.isFunder = response.is_funder; + vm.funderBounties = response.data; + }); + + }, + openBounties: function(user) { + let vm = this; + + vm.userSelected = user; + }, + sendInvite: function(bounty, user) { + let vm = this; + + console.log(vm.bountySelected, bounty, user, csrftoken); + let apiUrlInvite = '/api/v0.1/social_contribution_email/'; + let postInvite = fetchData( + apiUrlInvite, + 'POST', + {'usersId': [user], 'bountyId': bounty.id}, + {'X-CSRFToken': csrftoken} + ); + + $.when(postInvite).then((response) => { + console.log(response); + if (response.status === 500) { + _alert(response.msg, 'error'); + + } else { + vm.$refs['user-modal'].closeModal(); + _alert('The invitation has been sent', 'info'); + } + }); + }, + sendInviteAll: function(bountyUrl) { + let vm = this; + const apiUrlInvite = '/api/v0.1/bulk_invite/'; + const postInvite = fetchData( + apiUrlInvite, + 'POST', + {'params': vm.params, 'bountyId': bountyUrl}, + {'X-CSRFToken': csrftoken} + ); + + $.when(postInvite).then((response) => { + console.log(response); + if (response.status !== 200) { + _alert(response.msg, 'error'); + + } else { + vm.$refs['user-modal'].closeModal(); + _alert('The invitation has been sent', 'info'); + } + }); + + }, + getIssueDetails: function(url) { + let vm = this; + const apiUrldetails = `/actions/api/v0.1/bounties/?github_url=${encodeURIComponent(url)}`; + + vm.errorIssueDetails = undefined; + + if (url.indexOf('github.com/') < 0) { + vm.issueDetails = null; + vm.errorIssueDetails = 'Please paste a github issue url'; + return; + } + vm.issueDetails = undefined; + const getIssue = fetchData(apiUrldetails, 'GET'); + + $.when(getIssue).then((response) => { + if (response[0]) { + vm.issueDetails = response[0]; + vm.errorIssueDetails = undefined; + } else { + vm.issueDetails = null; + vm.errorIssueDetails = 'This issue wasn\'t bountied yet.'; + } + }); + + }, + closeModal() { + this.$refs['user-modal'].closeModal(); + }, + inviteOnMount: function() { + let vm = this; + + vm.contributorInvite = getURLParams('invite'); + vm.currentBounty = getURLParams('current-bounty'); + + if (vm.contributorInvite) { + let api = `/api/v0.1/users_fetch/?search=${vm.contributorInvite}`; + let getUsers = fetchData(api, 'GET'); + + $.when(getUsers).then(function(response) { + if (response && response.data.length) { + vm.openBounties(response.data[0]); + $('#userModal').bootstrapModal('show'); + } else { + _alert('The user was not found. Please try using the search box.', 'error'); + } + }); + } + }, + extractURLFilters: function() { + let vm = this; + let params = getURLParams(); + + vm.users = []; + + if (params) { + for (var prop in params) { + if (prop === 'skills') { + vm.$set(vm.params, prop, params[prop].split(',')); + } else { + vm.$set(vm.params, prop, params[prop]); + } + } + } + }, + joinTribe: function(user, event) { + event.target.disabled = true; + const url = `/tribe/${user.handle}/join/`; + const sendJoin = fetchData(url, 'POST', {}, {'X-CSRFToken': csrftoken}); + + $.when(sendJoin).then(function(response) { + event.target.disabled = false; + + if (response.is_member) { + ++user.follower_count; + user.is_following = true; + } else { + --user.follower_count; + user.is_following = false; + } + + event.target.classList.toggle('btn-outline-green'); + event.target.classList.toggle('btn-gc-blue'); + }).fail(function(error) { + event.target.disabled = false; + }); + } + } +}); +Vue = Vue.extend({ + delimiters: [ '[[', ']]' ] +}); + + +Vue.component('directory-card', { + name: 'DirectoryCard', + delimiters: [ '[[', ']]' ], + props: [ 'user', 'funderBounties' ] +}); +Vue.use(innerSearch.default); +Vue.component('autocomplete', { + props: [ 'options', 'value' ], + template: '#select2-template', + methods: { + formatMapping: function(item) { + console.log(item); + return item.name; + }, + formatMappingSelection: function(filter) { + return ''; + } + }, + mounted() { + let count = 0; + let vm = this; + + let data = $.map(this.options, function(obj, key) { + obj.id = count++; + obj.text = key; + return obj; + }); + + + $(vm.$el).select2({ + data: data, + multiple: true, + allowClear: true, + placeholder: 'Search for another filter to add', + minimumInputLength: 1, + escapeMarkup: function(markup) { + return markup; + } + }) + .on('change', function() { + console.log('changed'); + let val = $(vm.$el).val(); + + let changeData = $.map(val, function(filter) { + return data[filter]; + }); + + vm.$emit('input', changeData); + }); + + // fix for wrong position on select open + var select2Instance = $(vm.$el).data('select2'); + + select2Instance.on('results:message', function(params) { + this.dropdown._resizeDropdown(); + this.dropdown._positionDropdown(); + }); + }, + destroyed: function() { + $(this.$el).off().select2('destroy'); + this.$emit('destroyed'); + } +}); +Vue.component('user-directory', { + delimiters: [ '[[', ']]' ], + props: [ 'tribe', 'is_my_org' ], + data: function() { + return { + orgOwner: this.is_my_org || false, + userFilter: { + options: [ + {text: 'All', value: 'all'}, + {text: 'Tribe Owners', value: 'owners'}, + {text: 'Tribe Members', value: 'members'}, + {text: 'Tribe Hackers', value: 'hackers'} + ] + }, + tribeFilter: this.tribe || '', + users, + usersPage, + hackathonId, + usersNumPages, + usersHasNext, + numUsers, + media_url, + chatURL: document.chatURL || 'https://chat.gitcoin.co/', + searchTerm: null, + bottom: false, + params: { + 'user_filter': 'all' + }, + funderBounties: [], + currentBounty: undefined, + contributorInvite: undefined, + isFunder: false, + bountySelected: null, + userSelected: [], + showModal: false, + showFilters: true, + skills: document.keywords, + selectedSkills: [], + noResults: false, + isLoading: true, + gitcoinIssueUrl: '', + issueDetails: undefined, + errorIssueDetails: undefined, + showBanner: undefined, + persona: undefined, + hideFilterButton: !!document.getElementById('explore_tribes'), + expandFilter: true + }; + }, + + mounted() { + this.fetchUsers(); + this.tribeFilter = this.tribe; + this.$watch('params', function(newVal, oldVal) { + this.searchUsers(); + }, { + deep: true + }); + }, + created() { + if (document.contxt.github_handle && this.is_my_org) { + this.fetchBounties(); + } + this.inviteOnMount(); + this.extractURLFilters(); + }, + beforeMount() { + if (this.isMobile) { + this.showFilters = false; + } + window.addEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }, false); + }, + beforeDestroy() { + window.removeEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }); + } +}); +Vue.component('user-directory-elastic', { + delimiters: [ '[[', ']]' ], + data: function() { + return { + filters: [], + esColumns: [], + filterLoaded: false, + users, + usersPage, + usersNumPages, + usersHasNext, + numUsers, + media_url, + chatURL: document.chatURL || 'https://chat.gitcoin.co/', + searchTerm: null, + bottom: false, + params: {}, + funderBounties: [], + currentBounty: undefined, + contributorInvite: undefined, + isFunder: false, + bountySelected: null, + userSelected: [], + showModal: false, + showFilters: !document.getElementById('explore_tribes'), + skills: document.keywords, + selectedSkills: [], + noResults: false, + isLoading: true, + gitcoinIssueUrl: '', + issueDetails: undefined, + errorIssueDetails: undefined, + showBanner: undefined, + persona: undefined, + hideFilterButton: !!document.getElementById('explore_tribes') + }; + }, + methods: { + autoCompleteDestroyed: function() { + this.filters = []; + }, + autoCompleteChange: function(filters) { + this.filters = filters; + }, + outputToCSV: function() { + + let output = []; + + $.map(this.items, function(obj, key) { + output.push(obj.profile_id); + }); + + let url = '/api/v0.1/users_csv/'; + + $.get(url, {profile_ids: output}) + .then(resp => resp.blob()) + .then(blob => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + + a.style.display = 'none'; + a.href = url; + a.download = `users_csv-${Date.now()}.json`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + _alert('File download complete'); + }) + .catch(() => _alert('There was an issue downloading your file')); + }, + fetchMappings: function() { + let vm = this; + + $.when(vm.header.client.indices.getMapping()) + .then(response => { + vm.esColumns = response[vm.header.index]['mappings'][vm.header.type]['properties']; + vm.filterLoaded = true; + }); + } + }, + mounted() { + this.fetchMappings(); + // this.fetchUsers(); + this.$watch('params', function(newVal, oldVal) { + this.searchUsers(); + }, { + deep: true + }); + }, + created() { + this.setHost('https://elastic.androolloyd.com'); // TODO: set to proper env variable + this.setIndex('haystack'); + this.setType('modelresult'); + this.fetchBounties(); + this.inviteOnMount(); + this.extractURLFilters(); + }, + beforeMount() { + window.addEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }, false); + }, + beforeDestroy() { + window.removeEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }); + } +}); +if (document.getElementById('gc-users-directory')) { + + window.UserDirectory = new Vue({ + delimiters: [ '[[', ']]' ], + el: '#gc-users-directory', + data: { + filters: [], + esColumns: [], + filterLoaded: false, + users, + usersPage, + usersNumPages, + usersHasNext, + numUsers, + media_url, + chatURL: document.chatURL || 'https://chat.gitcoin.co/', + searchTerm: null, + bottom: false, + params: {}, + funderBounties: [], + currentBounty: undefined, + contributorInvite: undefined, + isFunder: false, + bountySelected: null, + userSelected: [], + showModal: false, + showFilters: !document.getElementById('explore_tribes'), + skills: document.keywords, + selectedSkills: [], + noResults: false, + isLoading: true, + gitcoinIssueUrl: '', + issueDetails: undefined, + errorIssueDetails: undefined, + showBanner: undefined, + persona: undefined, + hideFilterButton: !!document.getElementById('explore_tribes') + }, + methods: { + autoCompleteDestroyed: function() { + this.filters = []; + }, + autoCompleteChange: function(filters) { + this.filters = filters; + }, + outputToCSV: function() { + + let output = []; + + $.map(this.items, function(obj, key) { + output.push(obj._source.profile_id); + }); + + let url = `/api/v0.1/users_csv/?${$.param({profile_ids: output})}`; + + fetch(url) + .then(resp => resp.blob()) + .then(blob => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + + a.style.display = 'none'; + a.href = url; + a.download = `users_csv-${Date.now()}.csv`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + _alert('File download complete'); + }) + .catch((err) => { + console.log(err); + _alert('There was an issue downloading your file'); + }); + }, + + fetchMappings: function() { + let vm = this; + + $.when(vm.header.client.indices.getMapping()) + .then(response => { + vm.esColumns = response[vm.header.index]['mappings'][vm.header.type]['properties']; + vm.filterLoaded = true; + }); + } + }, + mounted() { + this.fetchMappings(); + this.fetch(this); + this.$watch('params', function(newVal, oldVal) { + this.searchUsers(); + }, { + deep: true + }); + }, + created() { + this.setHost(document.contxt.search_url ? document.contxt.search_url : 'https://elastic.gitcoin.co'); // TODO: set to proper env variable + this.setIndex('haystack'); + this.setType('modelresult'); + // this.extractURLFilters(); + }, + beforeMount() { + window.addEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }, false); + }, + beforeDestroy() { + window.removeEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }); + } + }); +} diff --git a/app/assets/v2/js/vue-components.js b/app/assets/v2/js/vue-components.js index 5f2a01be771..157b265b17d 100644 --- a/app/assets/v2/js/vue-components.js +++ b/app/assets/v2/js/vue-components.js @@ -78,12 +78,15 @@ Vue.component('modal', { Vue.component('select2', { - props: [ 'options', 'value' ], + props: [ 'options', 'value', 'placeholder', 'inputlength' ], template: '#select2-template', mounted: function() { let vm = this; - $(this.$el).select2({data: this.options}) + $(this.$el).select2({ + data: this.options, + placeHolder: this.placeholder !== null ? this.placeholder : 'filter here', + minimumInputLength: this.inputlength !== null ? this.inputlength : 1}) .val(this.value) .trigger('change') .on('change', function() { diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 73ee2b4002b..6ac80f9d127 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -2621,6 +2621,7 @@ def get_queryset(self): return ProfileQuerySet(self.model, using=self._db).slim() + class Repo(SuperModel): name = models.CharField(max_length=255) @@ -4186,6 +4187,9 @@ def to_representation(instance): } + def to_es(self): + return json.dumps(self.to_dict()) + def to_dict(self): """Get the dictionary representation with additional data. @@ -4346,6 +4350,7 @@ def to_dict(self): return context + @property def reassemble_profile_dict(self): params = self.as_dict @@ -4450,6 +4455,85 @@ def post_logout(sender, request, user, **kwargs): from dashboard.utils import create_user_action create_user_action(user, 'Logout', request) +class UserDirectoryQuerySet(models.QuerySet): + """Define the Profile QuerySet to be used as the objects manager.""" + +class UserDirectoryManager(models.Manager): + def get_queryset(self): + return UserDirectoryQuerySet(self.model, using=self._db) + +class UserDirectory(models.Model): + profile_id = models.CharField(max_length=255, primary_key=True) + join_date = models.CharField(max_length=255) + github_created_at = models.CharField(max_length=255) + first_name = models.CharField(max_length=255) + last_name = models.CharField(max_length=255) + email = models.EmailField() + handle = models.CharField(max_length=255) + sms_verification = models.BooleanField() + persona = models.CharField(max_length=255) + rank_coder = models.IntegerField() + num_hacks_joined = models.IntegerField() + which_hacks_joined = ArrayField(base_field=models.IntegerField()) + hack_work_starts = models.IntegerField() + hack_work_submits = models.IntegerField() + hack_work_start_orgs = models.IntegerField() + hack_work_submit_orgs = models.IntegerField() + bounty_work_starts = models.IntegerField() + bounty_work_submits = models.IntegerField() + hack_started_feature = models.IntegerField() + hack_started_code_review = models.IntegerField() + hack_started_security = models.IntegerField() + hack_started_design = models.IntegerField() + hack_started_documentation = models.IntegerField() + hack_started_bug = models.IntegerField() + hack_started_other = models.IntegerField() + hack_started_improvement = models.IntegerField() + started_feature = models.IntegerField() + started_code_review = models.IntegerField() + started_security = models.IntegerField() + started_design = models.IntegerField() + started_documentation = models.IntegerField() + started_bug = models.IntegerField() + started_other = models.IntegerField() + started_improvement = models.IntegerField() + submitted_feature = models.IntegerField() + submitted_code_review = models.IntegerField() + submitted_security = models.IntegerField() + submitted_design = models.IntegerField() + submitted_documentation = models.IntegerField() + submitted_bug = models.IntegerField() + submitted_other = models.IntegerField() + submitted_improvement = models.IntegerField() + bounty_earnings = models.IntegerField() + bounty_work_start_orgs = models.IntegerField() + bounty_work_submit_orgs = models.IntegerField() + kudos_sends = models.IntegerField() + kudos_receives = models.IntegerField() + hack_winner_kudos_received = models.IntegerField() + grants_opened = models.IntegerField() + grant_contributed = models.IntegerField() + grant_contributions = models.IntegerField() + grant_contribution_amount = models.IntegerField() + num_actions = models.IntegerField() + action_points = models.FloatField() + avg_points_per_action = models.FloatField() + last_action_on = models.IntegerField() + keywords = ArrayField(base_field=models.CharField(max_length=255)) + activity_level = models.CharField(max_length=255) + reliability = models.CharField(max_length=255) + average_rating = models.IntegerField() + longest_streak = models.IntegerField() + earnings_count = models.IntegerField() + follower_count = models.IntegerField() + following_count = models.IntegerField() + num_repeated_relationships = models.IntegerField() + verification_status = models.IntegerField() + + objects = UserDirectoryManager() + + class Meta: + managed = False class ProfileSerializer(serializers.BaseSerializer): """Handle serializing the Profile object.""" diff --git a/app/dashboard/router.py b/app/dashboard/router.py index 33669c904aa..625650d1222 100644 --- a/app/dashboard/router.py +++ b/app/dashboard/router.py @@ -33,7 +33,7 @@ from .models import ( Activity, Bounty, BountyFulfillment, BountyInvites, HackathonEvent, HackathonProject, Interest, Profile, - ProfileSerializer, SearchHistory, TribeMember, + ProfileSerializer, SearchHistory, TribeMember, UserDirectory ) from .tasks import increment_view_count @@ -219,6 +219,16 @@ class Meta: class HackathonProjectsPagination(PageNumberPagination): page_size = 10 +class UserDirectorySerializer(serializers.ModelSerializer): + + class Meta: + model = UserDirectory + fields = '__all__' + depth = 1 + +class UserDirectoryPagination(PageNumberPagination): + page_size = 20 + class HackathonProjectsViewSet(viewsets.ModelViewSet): queryset = HackathonProject.objects.prefetch_related('bounty', 'profiles').all().order_by('id') diff --git a/app/dashboard/search_indexes.py b/app/dashboard/search_indexes.py new file mode 100644 index 00000000000..96789275c49 --- /dev/null +++ b/app/dashboard/search_indexes.py @@ -0,0 +1,85 @@ +from haystack import indexes + +from .models import UserDirectory + +class UserDirectoryIndex(indexes.SearchIndex, indexes.Indexable): + text = indexes.CharField(document=True, use_template=True) + profile_id = indexes.IntegerField(null=True,model_attr='profile_id') + join_date = indexes.CharField(null=True,model_attr='join_date') + github_created_at = indexes.CharField(null=True,model_attr='github_created_at') + first_name = indexes.CharField(null=True,model_attr='first_name') + last_name = indexes.CharField(null=True,model_attr='last_name') + handle = indexes.CharField(null=True,model_attr='handle') + sms_verification = indexes.BooleanField(null=True,model_attr='sms_verification',faceted=True) + persona = indexes.CharField(null=True,model_attr='persona',faceted=True) + rank_coder = indexes.IntegerField(null=True,model_attr='rank_coder',faceted=True) + num_hacks_joined = indexes.IntegerField(null=True,model_attr='num_hacks_joined',faceted=True) + which_hacks_joined = indexes.MultiValueField(null=True,model_attr='which_hacks_joined',faceted=True) + hack_work_starts = indexes.IntegerField(null=True,model_attr='hack_work_starts',faceted=True) + hack_work_submits = indexes.IntegerField(null=True,model_attr='hack_work_submits',faceted=True) + hack_work_start_orgs = indexes.IntegerField(null=True,model_attr='hack_work_start_orgs',faceted=True) + hack_work_submit_orgs = indexes.IntegerField(null=True,model_attr='hack_work_submit_orgs',faceted=True) + bounty_work_starts = indexes.IntegerField(null=True,model_attr='bounty_work_starts',faceted=True) + bounty_work_submits = indexes.IntegerField(null=True,model_attr='bounty_work_submits',faceted=True) + hack_started_feature = indexes.IntegerField(null=True,model_attr='hack_started_feature',faceted=True) + hack_started_code_review = indexes.IntegerField(null=True,model_attr='hack_started_code_review',faceted=True) + hack_started_security = indexes.IntegerField(null=True,model_attr='hack_started_security',faceted=True) + hack_started_design = indexes.IntegerField(null=True,model_attr='hack_started_design',faceted=True) + hack_started_documentation = indexes.IntegerField(null=True,model_attr='hack_started_documentation',faceted=True) + hack_started_bug = indexes.IntegerField(null=True,model_attr='hack_started_bug',faceted=True) + hack_started_other = indexes.IntegerField(null=True,model_attr='hack_started_other',faceted=True) + hack_started_improvement = indexes.IntegerField(null=True,model_attr='hack_started_improvement',faceted=True) + started_feature = indexes.IntegerField(null=True,model_attr='started_feature',faceted=True) + started_code_review = indexes.IntegerField(null=True,model_attr='started_code_review',faceted=True) + started_security = indexes.IntegerField(null=True,model_attr='started_security',faceted=True) + started_design = indexes.IntegerField(null=True,model_attr='started_design',faceted=True) + started_documentation = indexes.IntegerField(null=True,model_attr='started_documentation',faceted=True) + started_bug = indexes.IntegerField(null=True,model_attr='started_bug',faceted=True) + started_other = indexes.IntegerField(null=True,model_attr='started_other',faceted=True) + started_improvement = indexes.IntegerField(null=True,model_attr='started_improvement',faceted=True) + submitted_feature = indexes.IntegerField(null=True,model_attr='submitted_feature',faceted=True) + submitted_code_review = indexes.IntegerField(null=True,model_attr='submitted_code_review',faceted=True) + submitted_security = indexes.IntegerField(null=True,model_attr='submitted_security',faceted=True) + submitted_design = indexes.IntegerField(null=True,model_attr='submitted_design',faceted=True) + submitted_documentation = indexes.IntegerField(null=True,model_attr='submitted_documentation',faceted=True) + submitted_bug = indexes.IntegerField(null=True,model_attr='submitted_bug',faceted=True) + submitted_other = indexes.IntegerField(null=True,model_attr='submitted_other',faceted=True) + submitted_improvement = indexes.IntegerField(null=True,model_attr='submitted_improvement',faceted=True) + bounty_earnings = indexes.IntegerField(null=True,model_attr='bounty_earnings',faceted=True) + bounty_work_start_orgs = indexes.IntegerField(null=True,model_attr='bounty_work_start_orgs',faceted=True) + bounty_work_submit_orgs = indexes.IntegerField(null=True,model_attr='bounty_work_submit_orgs',faceted=True) + kudos_sends = indexes.IntegerField(null=True,model_attr='kudos_sends',faceted=True) + kudos_receives = indexes.IntegerField(null=True,model_attr='kudos_receives',faceted=True) + hack_winner_kudos_received = indexes.IntegerField(null=True,model_attr='hack_winner_kudos_received',faceted=True) + grants_opened = indexes.IntegerField(null=True,model_attr='grants_opened',faceted=True) + grant_contributed = indexes.IntegerField(null=True,model_attr='grant_contributed',faceted=True) + grant_contributions = indexes.IntegerField(null=True,model_attr='grant_contributions',faceted=True) + grant_contribution_amount = indexes.IntegerField(null=True,model_attr='grant_contribution_amount',faceted=True) + num_actions = indexes.IntegerField(null=True,model_attr='num_actions',faceted=True) + action_points = indexes.IntegerField(null=True,model_attr='action_points',faceted=True) + avg_points_per_action = indexes.IntegerField(null=True,model_attr='avg_points_per_action',faceted=True) + last_action_on = indexes.CharField(null=True,model_attr='last_action_on') + keywords = indexes.MultiValueField(null=True,model_attr='keywords',faceted=True) + activity_level = indexes.CharField(null=True,model_attr='activity_level',faceted=True) + reliability = indexes.CharField(null=True,model_attr='reliability',faceted=True) + average_rating = indexes.IntegerField(null=True,model_attr='average_rating',faceted=True) + longest_streak = indexes.IntegerField(null=True,model_attr='longest_streak',faceted=True) + earnings_count = indexes.IntegerField(null=True,model_attr='earnings_count',faceted=True) + follower_count = indexes.IntegerField(null=True,model_attr='follower_count',faceted=True) + following_count = indexes.IntegerField(null=True,model_attr='following_count',faceted=True) + num_repeated_relationships = indexes.IntegerField(null=True,model_attr='num_repeated_relationships',faceted=True) + verification_status = indexes.CharField(null=True,model_attr='verification_status',faceted=True) + + # We add this for autocomplete. + handle_auto = indexes.EdgeNgramField(model_attr='handle') + keywords_auto = indexes.EdgeNgramField(model_attr='keywords') + first_name_auto = indexes.EdgeNgramField(null=True,model_attr='first_name') + last_name_auto = indexes.EdgeNgramField(null=True,model_attr='last_name') + persona_auto = indexes.EdgeNgramField(null=True,model_attr='persona') + + def get_model(self): + return UserDirectory + + def index_queryset(self, using=None): + """Used when the entire index for model is updated.""" + return self.get_model().objects.all() diff --git a/app/dashboard/templates/dashboard/users-elastic.html b/app/dashboard/templates/dashboard/users-elastic.html new file mode 100644 index 00000000000..7880630576d --- /dev/null +++ b/app/dashboard/templates/dashboard/users-elastic.html @@ -0,0 +1,287 @@ +{% comment %} + Copyright (C) 2020 Gitcoin Core + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +{% endcomment %} +{% load i18n static email_obfuscator add_url_schema avatar_tags %} + + + + + {% include 'shared/head.html' %} + {% include 'shared/cards.html' %} + + + + + + + + {% include 'shared/tag_manager_2.html' %} +
+ {% include 'shared/top_nav.html' with class='d-md-flex' %} + {% include 'home/nav.html' %} +
+
+
+ +
+ +
+ + + + {% csrf_token %} + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/footer.html' %} + + + + + + + + + diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 8c65faccce0..949e5d5c0fa 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -111,7 +111,7 @@ CoinRedemptionRequest, Coupon, Earning, FeedbackEntry, HackathonEvent, HackathonProject, HackathonRegistration, HackathonSponsor, HackathonWorkshop, Interest, LabsResearch, Option, Poll, PortfolioItem, Profile, ProfileSerializer, ProfileVerification, ProfileView, Question, SearchHistory, Sponsor, Subscription, Tool, ToolVote, - TribeMember, UserAction, UserVerificationModel, + TribeMember, UserAction, UserDirectory, UserVerificationModel ) from .notifications import ( maybe_market_tip_to_email, maybe_market_tip_to_github, maybe_market_tip_to_slack, maybe_market_to_email, @@ -869,6 +869,28 @@ def users_directory(request): return TemplateResponse(request, 'dashboard/users.html', params) +@staff_member_required +def users_directory_elastic(request): + """Handle displaying users directory page.""" + from retail.utils import programming_languages, programming_languages_full + + keywords = programming_languages + programming_languages_full + + params = { + 'is_staff': request.user.is_staff, + 'avatar_url': request.build_absolute_uri(static('v2/images/twitter_cards/tw_cards-07.png')) , + 'active': 'users', + 'title': 'Users', + 'meta_title': "", + 'meta_description': "", + 'keywords': keywords + } + + if request.path == '/tribes/explore': + params['explore'] = 'explore_tribes' + + return TemplateResponse(request, 'dashboard/users-elastic.html', params) + def users_fetch_filters(profile_list, skills, bounties_completed, leaderboard_rank, rating, organisation, hackathon_id = ""): if not settings.DEBUG: @@ -960,6 +982,34 @@ def set_project_notes(request): return JsonResponse({}) +@require_GET +def users_autocomplete(request): + max_items = 5 + q = request.GET.get('q') + if q: + from haystack.query import SQ, SearchQuerySet + sqs = SearchQuerySet().autocomplete((SQ(first_name_auto=q) | SQ(last_name_auto=q) | SQ(handle_auto=q))) + results = [str(result.object) for result in sqs[:max_items]] + else: + results = [] + + return JsonResponse({ + 'results': results + }) + + +@require_GET +def output_users_to_csv(request): + + if request.user.is_authenticated and not request.user.is_staff: + return Http404() + + profile_ids = request.GET.getlist('profile_ids[]') + + user_query = UserDirectory.objects.filter(profile_id__in=profile_ids) + from djqscsv import render_to_csv_response + return render_to_csv_response(user_query) + @require_GET def users_fetch(request): """Handle displaying users.""" @@ -1167,6 +1217,7 @@ def previous_worked(): return JsonResponse(params, status=200, safe=False) + @require_POST def bounty_mentor(request): diff --git a/app/retail/templates/shared/footer_scripts.html b/app/retail/templates/shared/footer_scripts.html index 574ec1830eb..1967560f128 100644 --- a/app/retail/templates/shared/footer_scripts.html +++ b/app/retail/templates/shared/footer_scripts.html @@ -31,6 +31,8 @@ {% else %} + + {% endif %} {% include 'shared/sentry.html' %} diff --git a/docker-compose.yml b/docker-compose.yml index 88a95e045ab..284b25e2f08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,8 @@ services: ports: - "8065:8065" volumes: - - ./chatconfig:/mattermost/config/ + - ./chatdata/config:/mattermost/config/ + - ./chatdata/root.html:/mattermost/client/root.html depends_on: - db deploy: @@ -32,6 +33,15 @@ services: reservations: memory: 128M + elasticsearch: + image: launcher.gcr.io/google/elasticsearch2 + ports: + - "9200:9200" + - "9300:9300" + volumes: + - ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro + + ipfs: image: ipfs/go-ipfs:release restart: unless-stopped diff --git a/elasticsearch.yml b/elasticsearch.yml new file mode 100644 index 00000000000..06a6c9eceba --- /dev/null +++ b/elasticsearch.yml @@ -0,0 +1,3 @@ +network.host: 0.0.0.0 +http.cors.enabled: true +http.cors.allow-origin: "*" diff --git a/requirements/base.txt b/requirements/base.txt index d1d44884804..1e09346782b 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,6 +7,7 @@ django-celery-beat==1.1.1 django==2.2.4 django-cors-headers==2.4.0 django-filter==2.0.0 +django-haystack django-ratelimit==1.1.0 djangorestframework==3.9.1 gitterpy @@ -26,6 +27,7 @@ python-twitter==3.2 sendgrid==5.6.0 slackclient==2.0.0 eth-tester +elasticsearch>=2.0.0,<3.0.0 websockets web3==4.5.0 eth-abi==1.1.1 @@ -93,7 +95,7 @@ redis==3.3.11 pandas wiki django-bulk-update -elasticsearch pdfrw django-admin-sortable2==0.7.6 twilio +django-queryset-csv diff --git a/scripts/crontab b/scripts/crontab index df25ffd5218..b829707ca39 100644 --- a/scripts/crontab +++ b/scripts/crontab @@ -103,6 +103,7 @@ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/us ## INFRASTRUCTURE */15 * * * * cd gitcoin/coin; bash scripts/run_management_command.bash warm_cache >> /var/log/gitcoin/warm_cache.log 2>&1 +*/15 * * * * cd gitcoin/coin; bash scripts/run_management_command.bash update_index >> /var/log/gitcoin/elastic_search.log 2>&1 * * * * * date >> /var/log/gitcoin/running_procs.log; ps -aux | grep python3 >> /var/log/gitcoin/running_procs.log 2>&1 1 1 * * * rm -f /var/log/gitcoin/running_procs.log From 23e3a39efa2a33757f8201f6d11cf1fcc9a1b5ef Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Mon, 31 Aug 2020 19:23:36 +0530 Subject: [PATCH 16/33] add missing migration --- .../migrations/0143_userdirectory.py | 88 +++++++++++++++++++ app/dashboard/router.py | 2 +- app/dashboard/search_indexes.py | 1 + app/dashboard/views.py | 2 +- 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 app/dashboard/migrations/0143_userdirectory.py diff --git a/app/dashboard/migrations/0143_userdirectory.py b/app/dashboard/migrations/0143_userdirectory.py new file mode 100644 index 00000000000..0a366be0710 --- /dev/null +++ b/app/dashboard/migrations/0143_userdirectory.py @@ -0,0 +1,88 @@ +# Generated by Django 2.2.4 on 2020-08-31 13:50 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dashboard', '0142_auto_20200818_0807'), + ] + + operations = [ + migrations.CreateModel( + name='UserDirectory', + fields=[ + ('profile_id', models.CharField(max_length=255, primary_key=True, serialize=False)), + ('join_date', models.CharField(max_length=255)), + ('github_created_at', models.CharField(max_length=255)), + ('first_name', models.CharField(max_length=255)), + ('last_name', models.CharField(max_length=255)), + ('email', models.EmailField(max_length=254)), + ('handle', models.CharField(max_length=255)), + ('sms_verification', models.BooleanField()), + ('persona', models.CharField(max_length=255)), + ('rank_coder', models.IntegerField()), + ('num_hacks_joined', models.IntegerField()), + ('which_hacks_joined', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), size=None)), + ('hack_work_starts', models.IntegerField()), + ('hack_work_submits', models.IntegerField()), + ('hack_work_start_orgs', models.IntegerField()), + ('hack_work_submit_orgs', models.IntegerField()), + ('bounty_work_starts', models.IntegerField()), + ('bounty_work_submits', models.IntegerField()), + ('hack_started_feature', models.IntegerField()), + ('hack_started_code_review', models.IntegerField()), + ('hack_started_security', models.IntegerField()), + ('hack_started_design', models.IntegerField()), + ('hack_started_documentation', models.IntegerField()), + ('hack_started_bug', models.IntegerField()), + ('hack_started_other', models.IntegerField()), + ('hack_started_improvement', models.IntegerField()), + ('started_feature', models.IntegerField()), + ('started_code_review', models.IntegerField()), + ('started_security', models.IntegerField()), + ('started_design', models.IntegerField()), + ('started_documentation', models.IntegerField()), + ('started_bug', models.IntegerField()), + ('started_other', models.IntegerField()), + ('started_improvement', models.IntegerField()), + ('submitted_feature', models.IntegerField()), + ('submitted_code_review', models.IntegerField()), + ('submitted_security', models.IntegerField()), + ('submitted_design', models.IntegerField()), + ('submitted_documentation', models.IntegerField()), + ('submitted_bug', models.IntegerField()), + ('submitted_other', models.IntegerField()), + ('submitted_improvement', models.IntegerField()), + ('bounty_earnings', models.IntegerField()), + ('bounty_work_start_orgs', models.IntegerField()), + ('bounty_work_submit_orgs', models.IntegerField()), + ('kudos_sends', models.IntegerField()), + ('kudos_receives', models.IntegerField()), + ('hack_winner_kudos_received', models.IntegerField()), + ('grants_opened', models.IntegerField()), + ('grant_contributed', models.IntegerField()), + ('grant_contributions', models.IntegerField()), + ('grant_contribution_amount', models.IntegerField()), + ('num_actions', models.IntegerField()), + ('action_points', models.FloatField()), + ('avg_points_per_action', models.FloatField()), + ('last_action_on', models.IntegerField()), + ('keywords', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), size=None)), + ('activity_level', models.CharField(max_length=255)), + ('reliability', models.CharField(max_length=255)), + ('average_rating', models.IntegerField()), + ('longest_streak', models.IntegerField()), + ('earnings_count', models.IntegerField()), + ('follower_count', models.IntegerField()), + ('following_count', models.IntegerField()), + ('num_repeated_relationships', models.IntegerField()), + ('verification_status', models.IntegerField()), + ], + options={ + 'managed': False, + }, + ), + ] diff --git a/app/dashboard/router.py b/app/dashboard/router.py index 625650d1222..15a13c647ab 100644 --- a/app/dashboard/router.py +++ b/app/dashboard/router.py @@ -33,7 +33,7 @@ from .models import ( Activity, Bounty, BountyFulfillment, BountyInvites, HackathonEvent, HackathonProject, Interest, Profile, - ProfileSerializer, SearchHistory, TribeMember, UserDirectory + ProfileSerializer, SearchHistory, TribeMember, UserDirectory, ) from .tasks import increment_view_count diff --git a/app/dashboard/search_indexes.py b/app/dashboard/search_indexes.py index 96789275c49..254d7aea0f2 100644 --- a/app/dashboard/search_indexes.py +++ b/app/dashboard/search_indexes.py @@ -2,6 +2,7 @@ from .models import UserDirectory + class UserDirectoryIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) profile_id = indexes.IntegerField(null=True,model_attr='profile_id') diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 949e5d5c0fa..eb022b56a80 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -111,7 +111,7 @@ CoinRedemptionRequest, Coupon, Earning, FeedbackEntry, HackathonEvent, HackathonProject, HackathonRegistration, HackathonSponsor, HackathonWorkshop, Interest, LabsResearch, Option, Poll, PortfolioItem, Profile, ProfileSerializer, ProfileVerification, ProfileView, Question, SearchHistory, Sponsor, Subscription, Tool, ToolVote, - TribeMember, UserAction, UserDirectory, UserVerificationModel + TribeMember, UserAction, UserDirectory, UserVerificationModel, ) from .notifications import ( maybe_market_tip_to_email, maybe_market_tip_to_github, maybe_market_tip_to_slack, maybe_market_to_email, From 33ae7f7c573f91087cafaee27eff2aae8f6deeeb Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Mon, 31 Aug 2020 11:06:27 -0300 Subject: [PATCH 17/33] revert docker-compose change for chat directories --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 284b25e2f08..e72e1d2f592 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,8 +21,7 @@ services: ports: - "8065:8065" volumes: - - ./chatdata/config:/mattermost/config/ - - ./chatdata/root.html:/mattermost/client/root.html + - ./chatconfig:/mattermost/config/ depends_on: - db deploy: From 462f317574449d78a5d614f5915e32c36e219de5 Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Mon, 31 Aug 2020 11:07:28 -0300 Subject: [PATCH 18/33] updated path for chat config directory --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e72e1d2f592..f3ab4475230 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: ports: - "8065:8065" volumes: - - ./chatconfig:/mattermost/config/ + - ./chatdata/config:/mattermost/config/ depends_on: - db deploy: From adabdc8784d77e0b1aaf357b87b69850c47a71b3 Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Mon, 31 Aug 2020 12:49:22 -0300 Subject: [PATCH 19/33] Bug fixes around the vue app(was loading incorrect js) (#7303) * Bug fixes around the vue app(was loading incorrect js) - added a authenticated proxy route to the elastic search cluster * added a check for settings.DEBUG for http/https --- app/app/context.py | 2 +- app/app/settings.py | 1 - app/app/urls.py | 1 + app/assets/v2/js/users-elastic.js | 4 ++-- app/dashboard/templates/dashboard/users-elastic.html | 2 +- app/dashboard/views.py | 12 ++++++++++++ requirements/base.txt | 1 + 7 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/app/context.py b/app/app/context.py index cad2a752c82..1c65eda51e7 100644 --- a/app/app/context.py +++ b/app/app/context.py @@ -134,7 +134,7 @@ def preprocess(request): 'MEDIA_URL': settings.MEDIA_URL, 'max_length': max_length, 'max_length_offset': max_length_offset, - 'search_url': settings.ELASTIC_SEARCH_LB_URL, + 'search_url': f'{settings.BASE_URL}user_lookup', 'chat_url': chat_url, 'base_url': settings.BASE_URL, 'chat_id': chat_id, diff --git a/app/app/settings.py b/app/app/settings.py index a70c9ae8599..2ac37cd061f 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -823,7 +823,6 @@ def callback(request): ELASTIC_SEARCH_URL = env('ELASTIC_SEARCH_URL', default='') -ELASTIC_SEARCH_LB_URL = env('ELASTIC_SEARCH_LB_URL', default='') HAYSTACK_CONNECTIONS = { 'default': { diff --git a/app/app/urls.py b/app/app/urls.py index 553c6abd377..57c8f715a09 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -371,6 +371,7 @@ # User Directory re_path(r'^users/?', dashboard.views.users_directory, name='users_directory'), re_path(r'^user_directory/?', dashboard.views.users_directory_elastic, name='users_directory_elastic'), + re_path(r'^user_lookup/?', dashboard.views.user_lookup, name='user_directory_lookup'), re_path(r'^tribes/explore', dashboard.views.users_directory, name='tribes_directory'), # Alpha functionality diff --git a/app/assets/v2/js/users-elastic.js b/app/assets/v2/js/users-elastic.js index 633d1dfb0ca..a10e2fc02d3 100644 --- a/app/assets/v2/js/users-elastic.js +++ b/app/assets/v2/js/users-elastic.js @@ -500,7 +500,7 @@ Vue.component('user-directory-elastic', { }); }, created() { - this.setHost('https://elastic.androolloyd.com'); // TODO: set to proper env variable + this.setHost(document.contxt.search_url); this.setIndex('haystack'); this.setType('modelresult'); this.fetchBounties(); @@ -613,7 +613,7 @@ if (document.getElementById('gc-users-directory')) { }); }, created() { - this.setHost(document.contxt.search_url ? document.contxt.search_url : 'https://elastic.gitcoin.co'); // TODO: set to proper env variable + this.setHost(document.contxt.search_url); this.setIndex('haystack'); this.setType('modelresult'); // this.extractURLFilters(); diff --git a/app/dashboard/templates/dashboard/users-elastic.html b/app/dashboard/templates/dashboard/users-elastic.html index 7880630576d..0404b71a4bb 100644 --- a/app/dashboard/templates/dashboard/users-elastic.html +++ b/app/dashboard/templates/dashboard/users-elastic.html @@ -282,6 +282,6 @@
const csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); document.keywords = {{keywords | safe}}; - + diff --git a/app/dashboard/views.py b/app/dashboard/views.py index eb022b56a80..4c14b63898a 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -869,6 +869,18 @@ def users_directory(request): return TemplateResponse(request, 'dashboard/users.html', params) +@csrf_exempt +@staff_member_required +def user_lookup(request): + + if not request.user.is_authenticated: + return HttpResponse(status=404) + + path = request.get_full_path().replace('/user_lookup', '') + remote_url = f'{"https" if not settings.DEBUG else "http"}://{settings.ELASTIC_SEARCH_URL}:9200{path}' + from proxy.views import proxy_view + return proxy_view(request, remote_url) + @staff_member_required def users_directory_elastic(request): """Handle displaying users directory page.""" diff --git a/requirements/base.txt b/requirements/base.txt index 1e09346782b..44027810181 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -99,3 +99,4 @@ pdfrw django-admin-sortable2==0.7.6 twilio django-queryset-csv +django-proxy==1.2.1 From 416670e2044247b581339ba333ebd77103c2ad47 Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Tue, 1 Sep 2020 00:35:26 +0530 Subject: [PATCH 20/33] fix / --- app/dashboard/sync/eth.py | 2 +- app/economy/tx.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/dashboard/sync/eth.py b/app/dashboard/sync/eth.py index 2dda4387dd7..8cd3b0292cb 100644 --- a/app/dashboard/sync/eth.py +++ b/app/dashboard/sync/eth.py @@ -80,7 +80,7 @@ def sync_eth_payout(fulfillment): def getReplacedTX(tx): ethurl = "https://etherscan.io/tx/" - response = requests.get(ethurl + tx, headers=headers) + response = requests.get(ethurl + tx + '/', headers=headers) soup = BeautifulSoup(response.content, "html.parser") p = soup.find("span", "u-label u-label--sm u-label--warning rounded") if not p: diff --git a/app/economy/tx.py b/app/economy/tx.py index aa550fa28fb..3c73651fa4d 100644 --- a/app/economy/tx.py +++ b/app/economy/tx.py @@ -53,7 +53,7 @@ class TransactionNotFound(Exception): # scrapes etherscan to get the replaced tx def getReplacedTX(tx): - response = requests.get(ethurl + tx, headers=headers) + response = requests.get(ethurl + tx + '/', headers=headers) soup = BeautifulSoup(response.content, "html.parser") # look for span that contains the dropped&replaced msg p = soup.find("span", "u-label u-label--sm u-label--warning rounded") From ff418d8dabab6ca0e2bd8ebaaadac56745af5297 Mon Sep 17 00:00:00 2001 From: owocki Date: Mon, 31 Aug 2020 13:10:33 -0600 Subject: [PATCH 21/33] updates quests, adds a fun little new earnings helper script. --- app/quests/templates/quests/index.html | 10 ++++++---- app/quests/views.py | 2 +- scripts/debug/earnings.py | 27 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 scripts/debug/earnings.py diff --git a/app/quests/templates/quests/index.html b/app/quests/templates/quests/index.html index 55b24d784f9..aceb23b508e 100644 --- a/app/quests/templates/quests/index.html +++ b/app/quests/templates/quests/index.html @@ -122,6 +122,10 @@

No Quests Found

{% if beaten %} Beaten + + Play Again + + {% elif cooldown %} @@ -155,12 +159,10 @@

No Quests Found

Leaderboard

-

Round 4 Challenge ended on 4/30!

+

Round 5 Challenge ended on 7/30!

- Congrats to the winners, announced here + Congrats to the winners!

-
Round 5 Challenge is now Live!
-

Starting with round 5, we will have more advanced anti-cheating congrats + will no longer be rewarding referrals as much. Whomever is in the top 3 of the leaderboard at end of day on 7/30/2020 will win a special 60 DAI prize and a very special Kudos.

diff --git a/app/quests/views.py b/app/quests/views.py index eeb80ffaecc..44c429f5587 100644 --- a/app/quests/views.py +++ b/app/quests/views.py @@ -29,7 +29,7 @@ logger = logging.getLogger(__name__) -current_round_number = 5 +current_round_number = 6 def next_quest(request): diff --git a/scripts/debug/earnings.py b/scripts/debug/earnings.py new file mode 100644 index 00000000000..cd488bb57e5 --- /dev/null +++ b/scripts/debug/earnings.py @@ -0,0 +1,27 @@ +from dashboard.models import Earning + +output = {} + +counter = 0 +queryset = Earning.objects.filter(network='mainnet') +for item in queryset.iter(): + counter += 1 + print(counter) + _from = '' + _to = '' + try: + _from = item.from_profile.handle + except: + pass + try: + _to = item.to_profile.handle + except: + pass + for key in [_from, _to]: + if key not in output.keys(): + output[key] = 0 + if item.value_usd: + output[key] += item.value_usd + +for key, val in output.items(): + print(key,",", val ) From bfb2f294ddb20ed775e7f66388ec11e89531aa53 Mon Sep 17 00:00:00 2001 From: Sanchay Mittal Date: Tue, 1 Sep 2020 15:41:57 +0530 Subject: [PATCH 22/33] New feature integration of circle to apollo hackathon (#7262) - Integration of circle to apollo hackathon only. - Replaces the old townsquare with gitcoin circle community. --- app/dashboard/templates/dashboard/index-vue.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/dashboard/templates/dashboard/index-vue.html b/app/dashboard/templates/dashboard/index-vue.html index ece938b6d87..72329c31e3a 100644 --- a/app/dashboard/templates/dashboard/index-vue.html +++ b/app/dashboard/templates/dashboard/index-vue.html @@ -198,7 +198,8 @@

Hackathon Coming Soon!

{% trans "Townsquare" %} -
+
+
From a480b7f8f814a10799131ba65b2a755a44d1538a Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Tue, 1 Sep 2020 17:07:21 +0530 Subject: [PATCH 23/33] bot notifs --- app/dashboard/models.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 6ac80f9d127..ff194bdc5a8 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -1087,25 +1087,20 @@ def is_notification_eligible(self, var_to_check=True): bool: Whether or not the Bounty is eligible for outbound notifications. """ - print(f'### GITCOIN BOT A2 var_to_check: {var_to_check}') - print(f'### GITCOIN BOT A2 get_natural_value: {self.get_natural_value()}') - a = self.get_natural_value() < 0.0001 - print(f'### GITCOIN BOT A2 get_natural_value < 0.0001: {a}') print(f'### GITCOIN BOT A2 network {self.network}') print(f'### GITCOIN BOT A2 settings.DEBUG {settings.DEBUG}') print(f'### GITCOIN BOT A2 settings.ENV {settings.ENV}') - print(f'### GITCOIN BOT A2 settings.GITHUB_API_USER {settings.GITHUB_API_USER}') - print(f'### GITCOIN BOT A2 self.github_org_name {self.github_org_name}') - if not var_to_check or self.get_natural_value() < 0.0001 or ( - self.network != settings.ENABLE_NOTIFICATIONS_ON_NETWORK): + if self.network != settings.ENABLE_NOTIFICATIONS_ON_NETWORK: return False + print(f'### GITCOIN BOT A3') + if self.network == 'mainnet' and (settings.DEBUG or settings.ENV != 'prod'): return False + print(f'### GITCOIN BOT A4') - if (settings.DEBUG or settings.ENV != 'prod') and settings.GITHUB_API_USER != self.github_org_name: - return False + print(f'### GITCOIN BOT A5 - Is Eligible') return True From f33f414dbe4163de25665141a36dd9e877566545 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Tue, 1 Sep 2020 16:39:22 -0300 Subject: [PATCH 24/33] fix user elastic directory --- app/assets/v2/js/notifications.js | 5 ++++- app/assets/v2/js/users-elastic.js | 9 +++------ app/dashboard/templates/dashboard/users-elastic.html | 11 ++++------- app/retail/templates/shared/footer_scripts.html | 5 ++--- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/app/assets/v2/js/notifications.js b/app/assets/v2/js/notifications.js index bd140572e84..804d9f1fa52 100644 --- a/app/assets/v2/js/notifications.js +++ b/app/assets/v2/js/notifications.js @@ -111,7 +111,10 @@ Vue.mixin({ }, computed: { sortedItems: function() { - return this.notifications.sort((a, b) => new Date(b.created_on) - new Date(a.created_on)); + if (!this.notifications) { + return; + } + return this.notifications.slice().sort((a, b) => new Date(b.created_on) - new Date(a.created_on)); } } diff --git a/app/assets/v2/js/users-elastic.js b/app/assets/v2/js/users-elastic.js index a10e2fc02d3..6994898287a 100644 --- a/app/assets/v2/js/users-elastic.js +++ b/app/assets/v2/js/users-elastic.js @@ -4,7 +4,6 @@ let usersNumPages = ''; let usersHasNext = false; let numUsers = ''; let hackathonId = document.hasOwnProperty('hackathon_id') ? document.hackathon_id : ''; -// let funderBounties = []; Vue.mixin({ methods: { @@ -25,9 +24,7 @@ Vue.mixin({ vm.usersPage = newPage; } vm.params.page = vm.usersPage; - if (hackathonId) { - vm.params.hackathon = hackathonId; - } + if (vm.searchTerm) { vm.params.search = vm.searchTerm; } else { @@ -518,11 +515,11 @@ Vue.component('user-directory-elastic', { }); } }); -if (document.getElementById('gc-users-directory')) { +if (document.getElementById('gc-users-elastic')) { window.UserDirectory = new Vue({ delimiters: [ '[[', ']]' ], - el: '#gc-users-directory', + el: '#gc-users-elastic', data: { filters: [], esColumns: [], diff --git a/app/dashboard/templates/dashboard/users-elastic.html b/app/dashboard/templates/dashboard/users-elastic.html index 0404b71a4bb..513d1bb2df3 100644 --- a/app/dashboard/templates/dashboard/users-elastic.html +++ b/app/dashboard/templates/dashboard/users-elastic.html @@ -34,7 +34,7 @@ {% include 'shared/top_nav.html' with class='d-md-flex' %} {% include 'home/nav.html' %}
-
+
+
-
-
+ - - {% csrf_token %} + {% include 'shared/footer.html' %} {% include 'shared/analytics.html' %} {% include 'shared/footer_scripts.html' with slim="1" %} - {% include 'shared/footer.html' %}