diff --git a/app/app/templates/shared/rollbar.html b/app/app/templates/shared/rollbar.html index 1a6f0cd72ed..37ce54cb9e3 100644 --- a/app/app/templates/shared/rollbar.html +++ b/app/app/templates/shared/rollbar.html @@ -26,6 +26,15 @@ environment: "{{ env }}" } }; + + {% if github_handle %} + _rollbarConfig.payload.person = { + id: {{ user.id }}, + username: "{{ github_handle }}", + email: "{{ user.email }}" + }; + {% endif %} + // Rollbar Snippet !function (r) { function e(n) { if (o[n]) return o[n].exports; var t = o[n] = { exports: {}, id: n, loaded: !1 }; return r[n].call(t.exports, t, t.exports, e), t.loaded = !0, t.exports } var o = {}; return e.m = r, e.c = o, e.p = "", e(0) }([function (r, e, o) { "use strict"; var n = o(1), t = o(4); _rollbarConfig = _rollbarConfig || {}, _rollbarConfig.rollbarJsUrl = _rollbarConfig.rollbarJsUrl || "https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/2.3.9/rollbar.min.js", _rollbarConfig.async = void 0 === _rollbarConfig.async || _rollbarConfig.async; var a = n.setupShim(window, _rollbarConfig), l = t(_rollbarConfig); window.rollbar = n.Rollbar, a.loadFull(window, document, !_rollbarConfig.async, _rollbarConfig, l) }, function (r, e, o) { "use strict"; function n(r) { return function () { try { return r.apply(this, arguments) } catch (r) { try { console.error("[Rollbar]: Internal error", r) } catch (r) { } } } } function t(r, e) { this.options = r, this._rollbarOldOnError = null; var o = s++; this.shimId = function () { return o }, "undefined" != typeof window && window._rollbarShims && (window._rollbarShims[o] = { handler: e, messages: [] }) } function a(r, e) { if (r) { var o = e.globalAlias || "Rollbar"; if ("object" == typeof r[o]) return r[o]; r._rollbarShims = {}, r._rollbarWrappedError = null; var t = new p(e); return n(function () { e.captureUncaught && (t._rollbarOldOnError = r.onerror, i.captureUncaughtExceptions(r, t, !0), i.wrapGlobals(r, t, !0)), e.captureUnhandledRejections && i.captureUnhandledRejections(r, t, !0); var n = e.autoInstrument; return e.enabled !== !1 && (void 0 === n || n === !0 || "object" == typeof n && n.network) && r.addEventListener && (r.addEventListener("load", t.captureLoad.bind(t)), r.addEventListener("DOMContentLoaded", t.captureDomContentLoaded.bind(t))), r[o] = t, t })() } } function l(r) { return n(function () { var e = this, o = Array.prototype.slice.call(arguments, 0), n = { shim: e, method: r, args: o, ts: new Date }; window._rollbarShims[this.shimId()].messages.push(n) }) } var i = o(2), s = 0, d = o(3), c = function (r, e) { return new t(r, e) }, p = d.bind(null, c); t.prototype.loadFull = function (r, e, o, t, a) { var l = function () { var e; if (void 0 === r._rollbarDidLoad) { e = new Error("rollbar.js did not load"); for (var o, n, t, l, i = 0; o = r._rollbarShims[i++];)for (o = o.messages || []; n = o.shift();)for (t = n.args || [], i = 0; i < t.length; ++i)if (l = t[i], "function" == typeof l) { l(e); break } } "function" == typeof a && a(e) }, i = !1, s = e.createElement("script"), d = e.getElementsByTagName("script")[0], c = d.parentNode; s.crossOrigin = "", s.src = t.rollbarJsUrl, o || (s.async = !0), s.onload = s.onreadystatechange = n(function () { if (!(i || this.readyState && "loaded" !== this.readyState && "complete" !== this.readyState)) { s.onload = s.onreadystatechange = null; try { c.removeChild(s) } catch (r) { } i = !0, l() } }), c.insertBefore(s, d) }, t.prototype.wrap = function (r, e, o) { try { var n; if (n = "function" == typeof e ? e : function () { return e || {} }, "function" != typeof r) return r; if (r._isWrap) return r; if (!r._rollbar_wrapped && (r._rollbar_wrapped = function () { o && "function" == typeof o && o.apply(this, arguments); try { return r.apply(this, arguments) } catch (o) { var e = o; throw "string" == typeof e && (e = new String(e)), e._rollbarContext = n() || {}, e._rollbarContext._wrappedSource = r.toString(), window._rollbarWrappedError = e, e } }, r._rollbar_wrapped._isWrap = !0, r.hasOwnProperty)) for (var t in r) r.hasOwnProperty(t) && (r._rollbar_wrapped[t] = r[t]); return r._rollbar_wrapped } catch (e) { return r } }; for (var u = "log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","), f = 0; f < u.length; ++f)t.prototype[u[f]] = l(u[f]); r.exports = { setupShim: a, Rollbar: p } }, function (r, e) { "use strict"; function o(r, e, o) { if (r) { var t; "function" == typeof e._rollbarOldOnError ? t = e._rollbarOldOnError : r.onerror && !r.onerror.belongsToShim && (t = r.onerror, e._rollbarOldOnError = t); var a = function () { var o = Array.prototype.slice.call(arguments, 0); n(r, e, t, o) }; a.belongsToShim = o, r.onerror = a } } function n(r, e, o, n) { r._rollbarWrappedError && (n[4] || (n[4] = r._rollbarWrappedError), n[5] || (n[5] = r._rollbarWrappedError._rollbarContext), r._rollbarWrappedError = null), e.handleUncaughtException.apply(e, n), o && o.apply(r, n) } function t(r, e, o) { if (r) { "function" == typeof r._rollbarURH && r._rollbarURH.belongsToShim && r.removeEventListener("unhandledrejection", r._rollbarURH); var n = function (r) { var o, n, t; try { o = r.reason } catch (r) { o = void 0 } try { n = r.promise } catch (r) { n = "[unhandledrejection] error getting `promise` from event" } try { t = r.detail, !o && t && (o = t.reason, n = t.promise) } catch (r) { t = "[unhandledrejection] error getting `detail` from event" } o || (o = "[unhandledrejection] error getting `reason` from event"), e && e.handleUnhandledRejection && e.handleUnhandledRejection(o, n) }; n.belongsToShim = o, r._rollbarURH = n, r.addEventListener("unhandledrejection", n) } } function a(r, e, o) { if (r) { var n, t, a = "EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(","); for (n = 0; n < a.length; ++n)t = a[n], r[t] && r[t].prototype && l(e, r[t].prototype, o) } } function l(r, e, o) { if (e.hasOwnProperty && e.hasOwnProperty("addEventListener")) { for (var n = e.addEventListener; n._rollbarOldAdd && n.belongsToShim;)n = n._rollbarOldAdd; var t = function (e, o, t) { n.call(this, e, r.wrap(o), t) }; t._rollbarOldAdd = n, t.belongsToShim = o, e.addEventListener = t; for (var a = e.removeEventListener; a._rollbarOldRemove && a.belongsToShim;)a = a._rollbarOldRemove; var l = function (r, e, o) { a.call(this, r, e && e._rollbar_wrapped || e, o) }; l._rollbarOldRemove = a, l.belongsToShim = o, e.removeEventListener = l } } r.exports = { captureUncaughtExceptions: o, captureUnhandledRejections: t, wrapGlobals: a } }, function (r, e) { "use strict"; function o(r, e) { this.impl = r(e, this), this.options = e, n(o.prototype) } function n(r) { for (var e = function (r) { return function () { var e = Array.prototype.slice.call(arguments, 0); if (this.impl[r]) return this.impl[r].apply(this.impl, e) } }, o = "log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad".split(","), n = 0; n < o.length; n++)r[o[n]] = e(o[n]) } o.prototype._swapAndProcessMessages = function (r, e) { this.impl = r(this.options); for (var o, n, t; o = e.shift();)n = o.method, t = o.args, this[n] && "function" == typeof this[n] && ("captureDomContentLoaded" === n || "captureLoad" === n ? this[n].apply(this, [t[0], o.ts]) : this[n].apply(this, t)); return this }, r.exports = o }, function (r, e) { "use strict"; r.exports = function (r) { return function (e) { if (!e && !window._rollbarInitialized) { r = r || {}; for (var o, n, t = r.globalAlias || "Rollbar", a = window.rollbar, l = function (r) { return new a(r) }, i = 0; o = window._rollbarShims[i++];)n || (n = o.handler), o.handler._swapAndProcessMessages(l, o.messages); window[t] = n, window._rollbarInitialized = !0 } } } }]); // End Rollbar Snippet diff --git a/app/assets/v2/js/pages/bounty_details.js b/app/assets/v2/js/pages/bounty_details.js index f48ed169453..442f4a185e5 100644 --- a/app/assets/v2/js/pages/bounty_details.js +++ b/app/assets/v2/js/pages/bounty_details.js @@ -724,7 +724,7 @@ var render_activity = function(result) { created_on: _interested.created, age: timeDifference(new Date(result['now']), new Date(_interested.created)), status: 'started', - uninterest_possible: isBountyOwner(result) + uninterest_possible: isBountyOwner(result) || document.isStaff }); }); } diff --git a/app/dashboard/notifications.py b/app/dashboard/notifications.py index 59890f733f2..3a3974c0780 100644 --- a/app/dashboard/notifications.py +++ b/app/dashboard/notifications.py @@ -736,7 +736,7 @@ def maybe_notify_bounty_user_removed_to_slack(bounty, username): #todo: DRY with expiration_start_work num_days_back_to_warn = 3 -num_days_back_to_delete_interest = 10 +num_days_back_to_delete_interest = 6 def maybe_notify_user_removed_github(bounty, username, last_heard_from_user_days=None): @@ -749,9 +749,9 @@ def maybe_notify_user_removed_github(bounty, username, last_heard_from_user_days status_header = get_status_header(bounty) - msg = f"""{status_header}@{username} has been removed from this issue due to inactivity ({last_heard_from_user_days} days) on the github thread. @{username} if you believe this was done in error, please go to the bounty and click 'start work' again. -* [x] warning 1 ({num_days_back_to_warn} days) -* [x] warning 2 ({num_days_back_to_warn * 2} days) + msg = f"""{status_header}@{username} has been removed for inactivity and [the issue]({bounty.url}) has been returned to an ‘Open’ Status. Let us know if you believe this has been done in error! + +* [x] warning ({num_days_back_to_warn} days) * [x] auto removal ({num_days_back_to_delete_interest} days) """ @@ -763,11 +763,8 @@ def maybe_warn_user_removed_github(bounty, username, last_heard_from_user_days): bounty.network != settings.ENABLE_NOTIFICATIONS_ON_NETWORK): return False - first_warning = 'x' - second_warning = 'x' if last_heard_from_user_days > num_days_back_to_warn else ' ' - msg = f"""@{username} are you still working on this issue? -* [{first_warning}] warning 1 ({num_days_back_to_warn} days) -* [{second_warning}] warning 2 ({num_days_back_to_warn * 2} days) + msg = f"""@{username} Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions! +* [x] warning ({num_days_back_to_warn} days) * [ ] auto removal ({num_days_back_to_delete_interest} days) """ diff --git a/app/dashboard/templates/bounty_details.html b/app/dashboard/templates/bounty_details.html index 4fcf5ba6b45..00944485c0d 100644 --- a/app/dashboard/templates/bounty_details.html +++ b/app/dashboard/templates/bounty_details.html @@ -295,6 +295,7 @@
{% trans "Funder" %}
document.issueURL = '{{issueURL}}'; document.issueNetwork = '{{network}}'; document.issue_stdbounties_id = '{{stdbounties_id}}'; + document.isStaff = '{{is_staff}}'; diff --git a/app/dashboard/templates/shared/sidebar_search.html b/app/dashboard/templates/shared/sidebar_search.html index c5ac55912c2..7a1260a9164 100644 --- a/app/dashboard/templates/shared/sidebar_search.html +++ b/app/dashboard/templates/shared/sidebar_search.html @@ -24,7 +24,7 @@
- +
@@ -54,7 +54,7 @@
- +
diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 7e3788a0dad..c26a5680777 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -289,7 +289,7 @@ def uninterested(request, bounty_id, profile_id): return JsonResponse({'errors': ['Bounty doesn\'t exist!']}, status=401) - if not bounty.is_funder(request.user.username.lower()): + if not bounty.is_funder(request.user.username.lower()) and not request.user.is_staff: return JsonResponse( {'error': 'Only bounty funders are allowed to remove users!'}, status=401) @@ -675,6 +675,7 @@ def bounty_details(request, ghuser='', ghrepo='', ghissue=0, stdbounties_id=None 'is_github_token_valid': is_github_token_valid(_access_token), 'github_auth_url': get_auth_url(request.path), "newsletter_headline": _("Be the first to know about new funded issues."), + 'is_staff': request.user.is_staff, } if issue_url: diff --git a/app/dataviz/d3_views.py b/app/dataviz/d3_views.py index 8967f63b47c..9d6a3b73754 100644 --- a/app/dataviz/d3_views.py +++ b/app/dataviz/d3_views.py @@ -1,6 +1,7 @@ import json import math import random +import time from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required @@ -532,6 +533,7 @@ def viz_graph(request, _type, template='graph'): source = bounty.org_name if source: for fulfillment in bounty.fulfillments.all(): + created = fulfillment.created_on.strftime("%s") if _type != 'fulfillments_accepted_only' or fulfillment.accepted: target = fulfillment.fulfiller_github_username.lower() if hide_pii: @@ -540,7 +542,7 @@ def viz_graph(request, _type, template='graph'): types[target] = 'target_accepted' if fulfillment.accepted else 'target' names[source] = None names[target] = None - edges.append((source, target, weight)) + edges.append((source, target, weight, created)) value = values.get(source, 0) value += weight @@ -550,7 +552,8 @@ def viz_graph(request, _type, template='graph'): values[target] = value for tip in Tip.objects.filter(network='mainnet'): - weight = bounty.value_in_usdt + weight = tip.value_in_usdt + created = tip.created_on.strftime("%s") if weight: source = tip.username.lower() if hide_pii: @@ -565,11 +568,12 @@ def viz_graph(request, _type, template='graph'): if source not in types.keys(): types[target] = 'target' names[target] = None - edges.append((source, target, weight)) + edges.append((source, target, weight, created)) if _type in ['what_future_could_look_like', 'all']: last_node = None + created = 1525147679 nodes = Profile.objects.exclude(github_access_token='').all() for profile in nodes: node = profile.handle.lower() @@ -585,7 +589,7 @@ def viz_graph(request, _type, template='graph'): target = nodes.order_by('?').first().handle.lower() if hide_pii: target = helper_hide_pii(target) - edges.append((target, node, weight)) + edges.append((target, node, weight, created)) last_node = node @@ -600,7 +604,7 @@ def viz_graph(request, _type, template='graph'): value = int(math.sqrt(math.sqrt(values.get(name, 1)))) output['nodes'].append({"name": name, 'value': value, 'type': types.get(name), 'avatar': avatars.get(name)}) for edge in edges: - source, target, weight = edge + source, target, weight, created = edge weight = math.sqrt(weight) if names.get(source) and names.get(target): source = names[source] @@ -610,6 +614,7 @@ def viz_graph(request, _type, template='graph'): 'target': target, 'value': value, 'weight': weight, + 'created': created, }) return JsonResponse(output) @@ -620,6 +625,7 @@ def viz_graph(request, _type, template='graph'): 'viz_type': _type, 'type_options': _type_options, 'page_route': page_route, + 'max_time': int(time.time()), } return TemplateResponse(request, f'dataviz/{template}.html', params) diff --git a/app/dataviz/templates/dataviz/graph.html b/app/dataviz/templates/dataviz/graph.html index 04a0020841f..6578abef564 100644 --- a/app/dataviz/templates/dataviz/graph.html +++ b/app/dataviz/templates/dataviz/graph.html @@ -37,22 +37,29 @@ @@ -63,87 +70,156 @@

{{comments}}

+ + + + +
+

Date

+
+ link = link.data(links); + + link.enter().insert("line", ".node") + .attr("class", "link"); + link.exit() + .remove(); + + force.start(); +} + + + {% endblock %} diff --git a/app/marketing/management/commands/expiration_start_work.py b/app/marketing/management/commands/expiration_start_work.py index e8eb9dd9180..56eae71d14e 100644 --- a/app/marketing/management/commands/expiration_start_work.py +++ b/app/marketing/management/commands/expiration_start_work.py @@ -49,7 +49,7 @@ def handle(self, *args, **options): # TODO: DRY with dashboard/notifications.py num_days_back_to_warn = 3 - num_days_back_to_delete_interest = 10 + num_days_back_to_delete_interest = 6 days = [i * 3 for i in range(1, 15)] days.reverse() diff --git a/app/marketing/management/commands/slackbot_welcome.py b/app/marketing/management/commands/slackbot_welcome.py deleted file mode 100644 index e8c05d657a6..00000000000 --- a/app/marketing/management/commands/slackbot_welcome.py +++ /dev/null @@ -1,77 +0,0 @@ -''' - Copyright (C) 2017 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 . - -''' -import os -import time - -from django.conf import settings -from django.core.management.base import BaseCommand - -from slackclient import SlackClient - - -MESSAGE=""" -Welcome to Gitcoin! :gitcoin: - -Gitcoin's mission is to *Grow Open Source*. - -A few quick facts: -- We are mission driven. Check out our mission: https://gitcoin.co/mission -- We aren't an ICO. There is no Gitcoin token. -- We are a community of blockchain developers. - -Here's how to get started in the community -- Say hey :wave: in #community-intros -- Join the community livestream every Friday at 5pm EST: https://gitcoin.co/livestream - -We believe that a great way to build new skills is to learn by doing, and Gitcoin's core product (Bounties) supports that. - -Here's how to get started with bounties: -- Developer? Check out the open bounties at https://gitcoin.co/explorer -- Repo Owner? Accellerate your dev progress by posting a bounty of your own at https://gitcoin.co/new -- Send a tip to any github username with https://gitcoin.co/tip - -We have more details in our onboarding guide at https://gitcoin.co/onboard . We also have a FAQ and tutorials available at https://gitcoin.co/help - -If you have any feedback for the team, or just want to say hi, this is them: -- @owocki, @vivek, @Pixelant, @mbeacom, @coderberry, @justin-bean - -See you around! :spock-hand: -Welcome_bot (and the Gitcoin Team) - -""" - -class Command(BaseCommand): - - help = 'pulls mailchimp emails' - - def handle(self, *args, **options): - sc = SlackClient(settings.SLACK_WELCOMEBOT_TOKEN) - if sc.rtm_connect(): - while True: - time.sleep(1) - new_evts = sc.rtm_read() - for evt in new_evts: - if "type" in evt: - print("-" + evt['type']) - if evt['type'] == 'team_join': - user_info=sc.api_call("users.info", user=evt['user']) - channel = sc.api_call("im.open", user=evt['user']['id']) - channel_id = channel['channel']['id'] - sc.api_call("chat.postMessage", channel=channel_id, unfurl_links=False, text=MESSAGE, as_user=True) - else: - print("Connection Failed, invalid token?") diff --git a/ops/lambdas/welcomebot/Makefile b/ops/lambdas/welcomebot/Makefile new file mode 100644 index 00000000000..6959c8a9308 --- /dev/null +++ b/ops/lambdas/welcomebot/Makefile @@ -0,0 +1,32 @@ +# Help +.DEFAULT_GOAL := help + +.PHONY: help + +init: ## Initialize the Zappa project for our AWS Lambda function and API Gateway. + @echo "Attempting to initialize Zappa" + @zappa init + @echo "Zappa initialized. Update domain_name and certificate_arn in zappa_settings.json, then run: make deploy && make certify" + +deploy: ## Deploy the welcomebot project for the first time. Run this once. + @echo "Deploying Welcomebot" + @zappa deploy welcomebot + @echo "Welcomebot deployed!" + +update: ## Update the welcomebot project. + @echo "Updating Welcomebot" + @zappa update welcomebot + @echo "Welcomebot updated!" + +undeploy: ## Undeploy the welcomebot. + @echo "Undeploying Welcomebot" + @zappa undeploy welcomebot + @echo "Welcomebot undeployed!" + +certify: ## Certify the AWS ACM certificate and establish your CNAME for welcomebot. + @echo "Certifying Welcomebot" + @zappa certify welcomebot + @echo "Welcomebot certified!" + +help: ## Display command information and usage instructions. Ex: make or make help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/ops/lambdas/welcomebot/README.md b/ops/lambdas/welcomebot/README.md new file mode 100644 index 00000000000..cd05b69da4a --- /dev/null +++ b/ops/lambdas/welcomebot/README.md @@ -0,0 +1,11 @@ +# Gitcoin Slack Welcomebot + +The Gitcoin Slack Welcomebot is a [Flask](http://flask.pocoo.org/) application meant to run on [AWS Lambda](https://aws.amazon.com/lambda/) using [Zappa](https://github.com/Miserlou/Zappa). + + +This bot is intended to be ran as Python 3.6 AWS Lambda function. + +Get started with the welcomebot by running `make init`. + +You will need to follow the Slack Bot setup instructions outlined in the [Python Slack Event API client](https://github.com/slackapi/python-slack-events-api) documentation. + diff --git a/ops/lambdas/welcomebot/app.py b/ops/lambdas/welcomebot/app.py new file mode 100644 index 00000000000..f093b875918 --- /dev/null +++ b/ops/lambdas/welcomebot/app.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +"""Define the Gitcoin Slack Welcome Bot flask app. + +Copyright (C) 2018 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 . + +""" +import os + +from flask import Flask, abort, jsonify, redirect, request +from slackclient import SlackClient +from slackeventsapi import SlackEventAdapter + +SLACK_VERIFICATION_TOKEN = os.environ.get('SLACK_VERIFICATION_TOKEN', '') +SLACK_WELCOMEBOT_TOKEN = os.environ.get('SLACK_WELCOMEBOT_TOKEN', '') +SLACK_TEAM_ID = os.environ.get('SLACK_TEAM_ID', '') +INDEX_REDIRECT_URL = os.environ.get('INDEX_REDIRECT_URL', 'https://gitcoin.co/slack') + +app = Flask(__name__) + + +def get_default_message(): + """Return the Gitcoin Welcomebot message.""" + return """ +Welcome to Gitcoin! :gitcoin: + +Gitcoin's mission is to *Grow Open Source*. + +A few quick facts: +- We are mission driven. Check out our mission: https://gitcoin.co/mission +- We aren't an ICO. There is no Gitcoin token. +- We are a community of blockchain developers. + +Here's how to get started in the community +- Say hey :wave: in #community-intros +- Join the community livestream every Friday at 5pm EST: https://gitcoin.co/livestream + +We believe that a great way to build new skills is to learn by doing, and Gitcoin's core product (Bounties) supports that. + +Here's how to get started with bounties: +- Developer? Check out the open bounties at https://gitcoin.co/explorer +- Repo Owner? Accelerate your dev progress by posting a bounty of your own at https://gitcoin.co/new +- Send a tip to any github username with https://gitcoin.co/tip + +We have more details in our onboarding guide at https://gitcoin.co/onboard . We also have a FAQ and tutorials available at https://gitcoin.co/help + +If you have any feedback for the team, or just want to say hi, this is them: +- @owocki, @vivek, @Pixelant, @mbeacom, @coderberry, @justin-bean + +See you around! :spock-hand: +Welcome_bot (and the Gitcoin Team) + +""" + + +MESSAGE = get_default_message() + + +def is_request_valid(request, team_id=''): + token_valid = request.form['token'] == SLACK_VERIFICATION_TOKEN + team_id_valid = True + + if team_id: + team_id_valid = request.form['team_id'] == team_id + + return token_valid and team_id_valid + + +@app.route('/') +def index(): + """Handle the index route.""" + return redirect(INDEX_REDIRECT_URL, code=302) + + +@app.route('/welcomebot', methods=['POST']) +def welcomebot(): + if not is_request_valid(request): + abort(400) + return jsonify(response_type='ephemeral', text=MESSAGE) + + +slack_events_adapter = SlackEventAdapter( + SLACK_VERIFICATION_TOKEN, '/slack/events', app) + + +@slack_events_adapter.on('team_join') +def new_user_welcome(event): + """Handle team_join slack events by sending the user our welcome message.""" + sc = SlackClient(SLACK_WELCOMEBOT_TOKEN) + user = event.get('event', {}).get('user', {}).get('id', '') + if user: + channel = sc.api_call('im.open', user=user) + channel_id = channel['channel']['id'] + sc.api_call( + 'chat.postMessage', + channel=channel_id, + unfurl_links=False, + text=MESSAGE, + as_user=True) + + +# Serve the slack welcomebot flask app. +if __name__ == '__main__': + app.run() diff --git a/ops/lambdas/welcomebot/requirements.txt b/ops/lambdas/welcomebot/requirements.txt new file mode 100644 index 00000000000..fce42b30a28 --- /dev/null +++ b/ops/lambdas/welcomebot/requirements.txt @@ -0,0 +1,4 @@ +zappa +flask +slackeventsapi +slackclient diff --git a/ops/lambdas/welcomebot/zappa_settings.json b/ops/lambdas/welcomebot/zappa_settings.json new file mode 100644 index 00000000000..52638ef221f --- /dev/null +++ b/ops/lambdas/welcomebot/zappa_settings.json @@ -0,0 +1,24 @@ +{ + "welcomebot": { + "app_function": "app.app", + "aws_region": "us-west-2", + "profile_name": "default", + "project_name": "welcomebot", + "runtime": "python3.6", + "s3_bucket": "", + "domain": "", + "slim_handler": true, + "certificate_arn": "", + "cloudwatch_log_level": "ERROR", + "exclude": [ + "Makefile", + "README.md", + "requirements.txt", + "zappa_settings.json" + ], + "timeout_seconds": 30, + "keep_warm": true, + "keep_warm_expression": "rate(5 minutes)", + "memory_size": 128 + } +} diff --git a/scripts/crontab b/scripts/crontab index be35d124148..a421f27152e 100644 --- a/scripts/crontab +++ b/scripts/crontab @@ -38,7 +38,6 @@ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/us 15 10 * * * cd gitcoin/coin; bash scripts/run_management_command_if_not_already_running.bash bounty_feedback_email >> /var/log/gitcoin/bounty_feedback_email.log 2>&1 15 11 * * * cd gitcoin/coin; bash scripts/run_management_command_if_not_already_running.bash new_bounties_email >> /var/log/gitcoin/new_bounties_email.log 2>&1 0 0 1 */3 * cd gitcoin/coin; bash scripts/run_management_command_if_not_already_running.bash send_quarterly_stats >> /var/log/gitcoin/send_quarterly_stats.log 2>&1 -*/15 * * * * cd gitcoin/coin; bash scripts/run_management_command_if_not_already_running.bash slackbot_welcome >> /var/log/gitcoin/slackbot_welcome.log 2>&1 11 10 * * * cd gitcoin/coin; bash scripts/run_management_command.bash expiration_tip >> /var/log/gitcoin/expiration_tip.log 2>&1