Skip to content

Commit 35f435b

Browse files
committed
Merge remote-tracking branch 'origin/stable'
2 parents 7953429 + 2f875b6 commit 35f435b

File tree

14 files changed

+365
-194
lines changed

14 files changed

+365
-194
lines changed

app/app/context.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,10 @@ def preprocess(request):
100100

101101
chat_unreads_request = chat_driver.teams.get_team_unreads_for_user(profile.chat_id)
102102

103-
if 'message' not in chat_unreads_request:
104-
for teams in chat_unreads_request:
105-
if teams['msg_count'] > 0 or teams['mention_count'] > 0:
106-
chat_unread_messages = True
107-
break
103+
for teams in chat_unreads_request:
104+
if teams['msg_count'] > 0 or teams['mention_count'] > 0:
105+
chat_unread_messages = True
106+
break
108107
except Exception as e:
109108
logger.error(str(e))
110109

app/app/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@
547547

548548
# Chat
549549
CHAT_PORT = env('CHAT_PORT', default=8065) # port of where mattermost is hosted
550-
CHAT_URL = env('CHAT_URL', default='http://localhost') # location of where mattermost is hosted
550+
CHAT_URL = env('CHAT_URL', default='localhost') # location of where mattermost is hosted
551551
CHAT_DRIVER_TOKEN = env('CHAT_DRIVER_TOKEN', default='') # driver token
552552
GITCOIN_HACK_CHAT_TEAM_ID = env('GITCOIN_HACK_CHAT_TEAM_ID', default='')
553553
GITCOIN_CHAT_TEAM_ID = env('GITCOIN_CHAT_TEAM_ID', default='')
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'''
2+
Copyright (C) 2018 Gitcoin Core
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Affero General Public License as published
6+
by the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Affero General Public License for more details.
13+
14+
You should have received a copy of the GNU Affero General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
'''
18+
import logging
19+
20+
from django.conf import settings
21+
from django.core.management.base import BaseCommand
22+
from django.db.models import Q
23+
from django.utils.text import slugify
24+
25+
from celery import group
26+
from chat.tasks import add_to_channel, create_channel, create_user, get_driver
27+
from chat.utils import create_channel_if_not_exists, create_user_if_not_exists
28+
from dashboard.models import Bounty, Interest, Profile
29+
30+
logger = logging.getLogger(__name__)
31+
32+
33+
class Command(BaseCommand):
34+
help = "Create channels for all active hackathon bounties"
35+
36+
def add_arguments(self, parser):
37+
# Positional arguments
38+
parser.add_argument('event_id', type=int, help="The event ID to synchronize bounties and channels for")
39+
40+
def handle(self, *args, **options):
41+
try:
42+
bounties_to_sync = Bounty.objects.filter(
43+
Q(event__pk=options['event_id'])
44+
)
45+
tasks = []
46+
for bounty in bounties_to_sync:
47+
profiles_to_connect = []
48+
try:
49+
funder_profile = Profile.objects.get(handle__iexact=bounty.bounty_owner_github_username.lower())
50+
51+
if funder_profile:
52+
if funder_profile.chat_id:
53+
created, funder_profile_request = create_user_if_not_exists(funder_profile)
54+
funder_profile.chat_id = funder_profile_request['id']
55+
funder_profile.save()
56+
profiles_to_connect.append(funder_profile.chat_id)
57+
for interest in bounty.interested.all():
58+
if interest.profile:
59+
if interest.profile.chat_id:
60+
created, chat_user = create_user_if_not_exists(interest.profile)
61+
interest.profile.chat_id = chat_user['id']
62+
interest.profile.save()
63+
profiles_to_connect.append(interest.profile.chat_id)
64+
if bounty.chat_channel_id is None or bounty.chat_channel_id is '':
65+
bounty_channel_name = slugify(f'{bounty.github_org_name}-{bounty.github_issue_number}')
66+
bounty_channel_name = bounty_channel_name[:60]
67+
create_channel_opts = {
68+
'team_id': settings.GITCOIN_HACK_CHAT_TEAM_ID,
69+
'channel_display_name': f'{bounty_channel_name}-{bounty.title}'[:60],
70+
'channel_name': bounty_channel_name[:60]
71+
}
72+
task = create_channel.s(create_channel_opts, bounty.id)
73+
task.link(add_to_channel.s(profiles_to_connect))
74+
else:
75+
task = add_to_channel.s({'id': bounty.chat_channel_id}, profiles_to_connect)
76+
tasks.append(task)
77+
except Exception as e:
78+
logger.error(str(e))
79+
continue
80+
if len(tasks) > 0:
81+
job = group(tasks)
82+
result = job.apply_async()
83+
else:
84+
print("Nothing to Sync")
85+
86+
except Exception as e:
87+
logger.error(str(e))

app/chat/tasks.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from app.redis_service import RedisService
44
from celery import app, group
55
from celery.utils.log import get_task_logger
6-
from dashboard.models import Profile
6+
from dashboard.models import Bounty, Profile
77
from mattermostdriver import Driver
8+
from mattermostdriver.exceptions import ResourceNotFound
89

910
logger = get_task_logger(__name__)
1011

@@ -13,11 +14,14 @@
1314
# Lock timeout of 2 minutes (just in the case that the application hangs to avoid a redis deadlock)
1415
LOCK_TIMEOUT = 60 * 2
1516

16-
chat_driver = Driver({
17+
driver_opts = {
18+
'scheme': 'https' if settings.CHAT_PORT == 443 else 'http',
1719
'url': settings.CHAT_URL,
1820
'port': settings.CHAT_PORT,
1921
'token': settings.CHAT_DRIVER_TOKEN
20-
})
22+
}
23+
24+
chat_driver = Driver(driver_opts)
2125

2226

2327
def get_driver():
@@ -26,23 +30,36 @@ def get_driver():
2630

2731

2832
@app.shared_task(bind=True, max_retries=3)
29-
def create_channel(self, options, retry: bool = True) -> None:
33+
def create_channel(self, options, bounty_id = None, retry: bool = True) -> None:
3034
"""
3135
:param options:
3236
:param retry:
3337
:return:
3438
"""
3539
with redis.lock("tasks:create_channel:%s" % options['channel_name'], timeout=LOCK_TIMEOUT):
3640

41+
chat_driver.login()
3742
try:
38-
chat_driver.login()
43+
channel_lookup = chat_driver.channels.get_channel_by_name(
44+
options['team_id'],
45+
options['channel_name']
46+
)
47+
if bounty_id:
48+
active_bounty = Bounty.objects.get(id=bounty_id)
49+
active_bounty.chat_channel_id = channel_lookup['id']
50+
active_bounty.save()
51+
return channel_lookup
52+
except ResourceNotFound as RNF:
3953
new_channel = chat_driver.channels.create_channel(options={
4054
'team_id': options['team_id'],
4155
'name': options['channel_name'],
4256
'display_name': options['channel_display_name'],
4357
'type': 'O'
4458
})
45-
59+
if bounty_id:
60+
active_bounty = Bounty.objects.get(id=bounty_id)
61+
active_bounty.chat_channel_id = new_channel['id']
62+
active_bounty.save()
4663
return new_channel
4764
except ConnectionError as exc:
4865
logger.info(str(exc))
@@ -54,26 +71,25 @@ def create_channel(self, options, retry: bool = True) -> None:
5471

5572

5673
@app.shared_task(bind=True, max_retries=3)
57-
def add_to_channel(self, options, retry: bool = True) -> None:
74+
def add_to_channel(self, channel_details, chat_user_ids, retry: bool = True) -> None:
5875
"""
59-
:param options:
76+
:param channel_details:
6077
:param retry:
6178
:return:
6279
"""
63-
with redis.lock("tasks:add_to_channel:%s" % options['channel_id'], timeout=LOCK_TIMEOUT):
64-
chat_driver.login()
65-
try:
66-
for x in options['profiles']:
67-
if x is not None:
68-
response = chat_driver.channels.add_user(options['channel_id'], options={
69-
'user_id': x
70-
})
71-
except ConnectionError as exc:
72-
logger.info(str(exc))
73-
logger.info("Retrying connection")
74-
self.retry(30)
75-
except Exception as e:
76-
logger.error(str(e))
80+
chat_driver.login()
81+
try:
82+
for chat_id in chat_user_ids:
83+
if chat_id:
84+
response = chat_driver.channels.add_user(channel_details['id'], options={
85+
'user_id': chat_id
86+
})
87+
except ConnectionError as exc:
88+
logger.info(str(exc))
89+
logger.info("Retrying connection")
90+
self.retry(30)
91+
except Exception as e:
92+
logger.error(str(e))
7793

7894

7995
@app.shared_task(bind=True, max_retries=1)
@@ -113,9 +129,12 @@ def update_user(self, query_opts, update_opts, retry: bool = True) -> None:
113129
try:
114130

115131
if query_opts['chat_id'] is None:
116-
chat_user = chat_driver.users.get_user_by_username(query_opts['handle'])
117-
if 'message' not in chat_user:
132+
try:
133+
134+
chat_user = chat_driver.users.get_user_by_username(query_opts['handle'])
118135
chat_id = chat_user['id']
136+
except Exception as e:
137+
logger.info(f"Unable to find chat user for {query_opts['handle']}")
119138
else:
120139
chat_id = query_opts['chat_id']
121140

app/chat/utils.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'''
2+
Copyright (C) 2018 Gitcoin Core
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Affero General Public License as published
6+
by the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Affero General Public License for more details.
13+
14+
You should have received a copy of the GNU Affero General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
'''
18+
import logging
19+
20+
from django.conf import settings
21+
22+
from celery import group
23+
from chat.tasks import add_to_channel, create_channel, create_user, get_driver
24+
from dashboard.models import Profile
25+
from mattermostdriver.exceptions import ResourceNotFound
26+
27+
logger = logging.getLogger(__name__)
28+
29+
30+
def create_channel_if_not_exists(channel_opts):
31+
try:
32+
chat_driver = get_driver()
33+
34+
channel_lookup_response = chat_driver.channels.get_channel_by_name(
35+
channel_opts['team_id'], channel_opts['channel_name']
36+
)
37+
return False, channel_lookup_response
38+
39+
except ResourceNotFound as RNF:
40+
try:
41+
result = create_channel.apply_async(args=[channel_opts])
42+
bounty_channel_id_response = result.get()
43+
44+
if 'message' in bounty_channel_id_response:
45+
raise ValueError(bounty_channel_id_response['message'])
46+
47+
return True, bounty_channel_id_response
48+
except Exception as e:
49+
logger.error(str(e))
50+
51+
52+
def create_user_if_not_exists(profile):
53+
try:
54+
chat_driver = get_driver()
55+
56+
current_chat_user = chat_driver.users.get_user_by_username(profile.handle)
57+
profile.chat_id = current_chat_user['id']
58+
profile.save()
59+
return False, current_chat_user
60+
except ResourceNotFound as RNF:
61+
new_user_request = create_user.apply_async(args=[{
62+
"email": profile.user.email,
63+
"username": profile.handle,
64+
"first_name": profile.user.first_name,
65+
"last_name": profile.user.last_name,
66+
"nickname": profile.handle,
67+
"auth_data": f'{profile.user.id}',
68+
"auth_service": "gitcoin",
69+
"locale": "en",
70+
"props": {},
71+
"notify_props": {
72+
"email": "false",
73+
"push": "mention",
74+
"desktop": "all",
75+
"desktop_sound": "true",
76+
"mention_keys": f'{profile.handle}, @{profile.handle}',
77+
"channel": "true",
78+
"first_name": "false"
79+
},
80+
}, {
81+
"tid": settings.GITCOIN_HACK_CHAT_TEAM_ID
82+
}])
83+
84+
return True, new_user_request.get()

app/chat/views.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,24 @@
2222
from django.template.response import TemplateResponse
2323
from django.utils.translation import gettext_lazy as _
2424

25-
from chat.tasks import get_driver
25+
from marketing.models import Stat
2626

2727

2828
def chat(request):
2929
"""Render chat landing page response."""
3030

3131
try:
32-
chat_driver = get_driver()
32+
users_online_count = Stat.objects.get(key='chat_active_users')
33+
users_total_count = Stat.objects.get(key='chat_total_users')
3334

34-
chat_stats = chat_driver.teams.get_team_stats(settings.GITCOIN_CHAT_TEAM_ID)
35-
if 'message' not in chat_stats:
36-
users_online = chat_stats['active_member_count']
37-
else:
38-
users_online = 'N/A'
3935
except Exception as e:
40-
print(str(e))
41-
users_online = 'N/A'
36+
users_online_count = 'N/A'
37+
users_total_count = 'N/A'
4238
context = {
43-
'users_online': users_online,
39+
'users_online': users_online_count,
40+
'users_count': users_total_count,
4441
'title': "Chat",
45-
'cards_desc': f"Gitcoin chat has {users_online} users online now!"
42+
'cards_desc': f"Gitcoin chat has {users_online_count} users online now!"
4643
}
4744

4845
return TemplateResponse(request, 'chat.html', context)

app/dashboard/tasks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from app.redis_service import RedisService
44
from celery import app, group
55
from celery.utils.log import get_task_logger
6+
from chat.tasks import create_channel
67
from dashboard.models import Profile
78
from marketing.mails import func_name, send_mail
89
from retail.emails import render_share_bounty
@@ -27,7 +28,7 @@ def bounty_on_create(self, team_id, new_bounty, retry: bool = True) -> None:
2728
'team_id': team_id,
2829
'channel_name': f'bounty-{new_bounty.id}',
2930
'channel_display_name': f'bounty-{new_bounty.id}'
30-
})
31+
}, new_bounty.id)
3132
)
3233

3334
# what has to happen that we can issue without a dependency from any subtasks?

0 commit comments

Comments
 (0)