Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ce/connect messaging #35364

Open
wants to merge 57 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
0043322
add models and rename function
calellowitz Nov 14, 2024
7bc3854
add connect backend
calellowitz Nov 14, 2024
c609f0d
add connectid settings and urls
calellowitz Nov 14, 2024
e4b6deb
add connect messages to scheduling forms
calellowitz Nov 14, 2024
929204d
add necessary auth changes
calellowitz Nov 14, 2024
79154f0
update domain deletion code for new models
calellowitz Nov 14, 2024
460c794
use updated method name
calellowitz Nov 14, 2024
092dbe0
implement survey model
calellowitz Nov 15, 2024
f29ebfc
fix default values
calellowitz Nov 15, 2024
91ce6e6
move content fields to top level of payload
calellowitz Nov 22, 2024
4b992c7
use string for the key
calellowitz Nov 26, 2024
36a800f
return the value
calellowitz Nov 26, 2024
472609a
typo
calellowitz Nov 26, 2024
1efd62e
incoming messaging processing updates
calellowitz Nov 27, 2024
80e0345
handle connect content
calellowitz Nov 27, 2024
f327d55
add create channel views
calellowitz Nov 28, 2024
6d9e562
add connect survey content
calellowitz Nov 28, 2024
8421124
update models for connect message survey
calellowitz Nov 28, 2024
e725557
return context
calellowitz Nov 28, 2024
a9b2f08
correct template name
calellowitz Nov 28, 2024
d5935d2
use correct context name
calellowitz Nov 28, 2024
7a59b8e
add imports
calellowitz Nov 28, 2024
ef87ae1
update view args
calellowitz Nov 28, 2024
7680272
handle incorrect username
calellowitz Dec 1, 2024
921d8be
add urls to sidebar
calellowitz Dec 2, 2024
70bce3c
require toggle
calellowitz Dec 3, 2024
a3b39ff
fix f strings
calellowitz Dec 4, 2024
f1a02f8
fix typo in view name
calellowitz Dec 4, 2024
7be46a3
Update corehq/messaging/scheduling/forms.py
calellowitz Dec 5, 2024
370bab0
use message instead of payload dict
calellowitz Dec 9, 2024
d869378
Merge branch 'master' into ce/connect-messaging
orangejenny Dec 10, 2024
63f9b29
return url list
calellowitz Dec 12, 2024
5b85e3f
fix auth and pull out key generation
calellowitz Dec 12, 2024
11a6e7f
create key on sending first message if not created yet
calellowitz Dec 12, 2024
00fd753
add missing imports
calellowitz Dec 13, 2024
e810011
fix typo in form initial values
calellowitz Dec 13, 2024
2c5b5fc
set message id on init
calellowitz Dec 13, 2024
5de9a9f
missing import and definition
calellowitz Dec 17, 2024
0231864
use domain instead of domain_scope
calellowitz Dec 19, 2024
596eba4
fix function error
calellowitz Dec 19, 2024
0fa683e
pr feedback and report error
calellowitz Dec 19, 2024
c67ac9e
update report
calellowitz Dec 19, 2024
10737b7
lint
calellowitz Dec 19, 2024
8dd41f1
convert to f strings
calellowitz Dec 19, 2024
a66ec40
move copy and lint
calellowitz Dec 19, 2024
c047e8e
move critical section to base class
calellowitz Dec 19, 2024
75184ce
make try block narrower
calellowitz Dec 19, 2024
7d66924
add success message to channel creation
calellowitz Dec 19, 2024
b652ddb
replace get_sms_class
calellowitz Dec 19, 2024
b28cdbf
update sms docs
calellowitz Dec 19, 2024
867dd47
Merge branch 'master' into ce/connect-messaging
calellowitz Dec 19, 2024
f50e557
fix bad merge
calellowitz Dec 19, 2024
eccc425
squash migrations
calellowitz Dec 19, 2024
ae2d380
replace method with property
calellowitz Dec 19, 2024
aaffbaa
add new models to domain deletion and dump
calellowitz Dec 19, 2024
7142730
add more models to domain deletion
calellowitz Dec 19, 2024
599af78
fix bug in channel creation and improve message
calellowitz Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions corehq/apps/commtrack/sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from corehq.apps.domain.models import Domain
from corehq.apps.products.models import SQLProduct
from corehq.apps.receiverwrapper.util import submit_form_locally
from corehq.apps.sms.api import MessageMetadata, send_sms_to_verified_number
from corehq.apps.sms.api import MessageMetadata, send_message_to_verified_number
from corehq.apps.users.models import CouchUser
from corehq.form_processor.interfaces.supply import SupplyInterface
from corehq.form_processor.parsers.ledgers.helpers import (
Expand Down Expand Up @@ -50,7 +50,7 @@ def handle(verified_contact, text, msg):
except Exception as e:
if settings.UNIT_TESTING or settings.DEBUG:
raise
send_sms_to_verified_number(verified_contact, 'problem with stock report: %s' % str(e))
send_message_to_verified_number(verified_contact, 'problem with stock report: %s' % str(e))
return True

process(domain_obj.name, data)
Expand Down Expand Up @@ -163,7 +163,7 @@ def single_action_transactions(self, action, args):

try:
value = int(arg)
except:
except ValueError:
raise SMSError('could not understand product quantity "%s"' % arg)

for product in products:
Expand Down Expand Up @@ -268,9 +268,9 @@ def process_transfers(E, transfers):
if transfers[0].action in [
const.StockActions.RECEIPTS,
]:
here, there = ('dest', 'src')
here, there = ('dest', 'src') # noqa: F841
else:
here, there = ('src', 'dest')
here, there = ('src', 'dest') # noqa: F841

attr[here] = transfers[0].case_id

Expand Down Expand Up @@ -350,7 +350,7 @@ def to_instance(data):

def truncate(text, maxlen, ellipsis='...'):
if len(text) > maxlen:
return text[:maxlen-len(ellipsis)] + ellipsis
return text[:(maxlen - len(ellipsis))] + ellipsis
else:
return text

Expand All @@ -361,7 +361,9 @@ def send_confirmation(v, data):
static_loc = data['location']
location_name = static_loc.name
metadata = MessageMetadata(location_id=static_loc.get_id)
tx_by_action = map_reduce(lambda tx: [(tx.action_config(C).name,)], data=data['transactions'], include_docs=True)
tx_by_action = map_reduce(lambda tx: [(tx.action_config(C).name,)],
data=data['transactions'],
include_docs=True)

def summarize_action(action, txs):
return '%s %s' % (txs[0].action_config(C).keyword.upper(), ' '.join(sorted(tx.fragment() for tx in txs)))
Expand All @@ -372,4 +374,4 @@ def summarize_action(action, txs):
' '.join(sorted(summarize_action(a, txs) for a, txs in tx_by_action.items()))
)

send_sms_to_verified_number(v, msg, metadata=metadata)
send_message_to_verified_number(v, msg, metadata=metadata)
19 changes: 18 additions & 1 deletion corehq/apps/domain/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.models import User
from django.db.models import Q
from django.http import HttpResponse
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.views.decorators.debug import sensitive_variables

from no_exceptions.exceptions import Http400
Expand Down Expand Up @@ -405,3 +405,20 @@ def user_can_access_domain_specific_pages(request):
return False

return couch_user.is_member_of(project) or (couch_user.is_superuser and not project.restrict_superusers)


def connectid_token_auth(view_func):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird that connectid is one word. I do see that it's consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think of connectid as its own thing (its a separate repo and site than connect), using one word feels like it implies that, rather than the id for connect, but I am not wedded to it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connectid and "the id for connect` being two separate concepts sounds confusing, but I don't think I have enough context on the project to be certain or to make a better suggestion.

@wraps(view_func)
def _inner(request, *args, **kwargs):
auth_header = request.META.get("HTTP_AUTHORIZATION")
if not auth_header:
return HttpResponseForbidden()
_, token = auth_header.split(" ")
if not token:
return HttpResponseBadRequest("ConnectID Token Required")
username = get_connectid_userinfo(token)
if username is None:
return HttpResponseForbidden()
request.connectid_username = username
return view_func(request, *args, **kwargs)
return _inner
7 changes: 5 additions & 2 deletions corehq/apps/domain/deletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def _delete_sms_content_events_schedules(domain_name):
models = [
'SMSContent', 'EmailContent', 'SMSSurveyContent',
'IVRSurveyContent', 'SMSCallbackContent', 'CustomContent',
'FCMNotificationContent'
'FCMNotificationContent', 'ConnectMessageContent', 'ConnectMessageSurveyContent'
]
filters = [
'alertevent__schedule__domain',
Expand Down Expand Up @@ -325,6 +325,7 @@ def _delete_demo_user_restores(domain_name):
ModelDeletion('sms', 'DailyOutboundSMSLimitReached', 'domain'),
ModelDeletion('sms', 'SMS', 'domain'),
ModelDeletion('sms', 'Email', 'domain'),
ModelDeletion('sms', 'ConnectMessage', 'domain'),
ModelDeletion('sms', 'SQLLastReadMessage', 'domain'),
ModelDeletion('sms', 'ExpectedCallback', 'domain'),
ModelDeletion('ivr', 'Call', 'domain'),
Expand Down Expand Up @@ -401,7 +402,8 @@ def _delete_demo_user_restores(domain_name):
ModelDeletion('linked_domain', 'DomainLink', 'linked_domain', ['DomainLinkHistory']),
CustomDeletion('scheduling', _delete_sms_content_events_schedules, [
'SMSContent', 'EmailContent', 'SMSSurveyContent',
'IVRSurveyContent', 'SMSCallbackContent', 'CustomContent', 'FCMNotificationContent'
'IVRSurveyContent', 'SMSCallbackContent', 'CustomContent', 'FCMNotificationContent',
'ConnectMessageContent', 'ConnectMessageSurveyContent'
]),
ModelDeletion('scheduling', 'MigratedReminder', 'broadcast__domain'),
ModelDeletion('scheduling', 'AlertEvent', 'schedule__domain'),
Expand Down Expand Up @@ -457,6 +459,7 @@ def _delete_demo_user_restores(domain_name):
ModelDeletion('userreports', 'InvalidUCRData', 'domain'),
ModelDeletion('userreports', 'UCRExpression', 'domain'),
ModelDeletion('users', 'ConnectIDUserLink', 'domain'),
ModelDeletion('users', 'ConnectIDMessagingKey', 'domain'),
ModelDeletion('users', 'DomainRequest', 'domain'),
ModelDeletion('users', 'DeactivateMobileWorkerTrigger', 'domain'),
ModelDeletion('users', 'Invitation', 'domain'),
Expand Down
2 changes: 2 additions & 0 deletions corehq/apps/domain/tests/test_deletion_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
'util.ComplaintBounceMeta',
'util.PermanentBounceMeta',
'util.TransientBounceEmail',
'users.ConnectIDUserLink', # handled by cascading delete of django user model
'users.ConnectIDMessagingKey', # handled by cascading delete of user link model (from user model)
'users.UserHistory',
}

Expand Down
18 changes: 18 additions & 0 deletions corehq/apps/dump_reload/sql/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@
SimpleFilter('randomtimedevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.FCMNotificationContent',
SimpleFilter('casepropertytimedevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageContent',
SimpleFilter('alertevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageContent',
SimpleFilter('timedevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageContent',
SimpleFilter('randomtimedevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageContent',
SimpleFilter('casepropertytimedevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageSurveyContent',
SimpleFilter('alertevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageSurveyContent',
SimpleFilter('timedevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageSurveyContent',
SimpleFilter('randomtimedevent__schedule__domain')),
UniqueFilteredModelIteratorBuilder('scheduling.ConnectMessageSurveyContent',
SimpleFilter('casepropertytimedevent__schedule__domain')),
FilteredModelIteratorBuilder('scheduling.AlertSchedule', SimpleFilter('domain')),
FilteredModelIteratorBuilder('scheduling.AlertEvent', SimpleFilter('schedule__domain')),
FilteredModelIteratorBuilder('scheduling.ImmediateBroadcast', SimpleFilter('domain')),
Expand Down Expand Up @@ -155,6 +171,7 @@
FilteredModelIteratorBuilder('sms.MessagingSubEvent', SimpleFilter('domain')),
FilteredModelIteratorBuilder('sms.PhoneNumber', SimpleFilter('domain')),
FilteredModelIteratorBuilder('sms.SMS', SimpleFilter('domain')),
FilteredModelIteratorBuilder('sms.ConnectMessage', SimpleFilter('domain')),
FilteredModelIteratorBuilder('sms.Keyword', SimpleFilter('domain')),
FilteredModelIteratorBuilder('sms.KeywordAction', SimpleFilter('keyword__domain')),
FilteredModelIteratorBuilder('sms.QueuedSMS', SimpleFilter('domain')),
Expand All @@ -166,6 +183,7 @@
FilteredModelIteratorBuilder('linked_domain.DomainLinkHistory', SimpleFilter('link__linked_domain')),
FilteredModelIteratorBuilder('user_importer.UserUploadRecord', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.ConnectIDUserLink', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.ConnectIDMessagingKey', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.DeactivateMobileWorkerTrigger', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.DomainRequest', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.Invitation', SimpleFilter('domain')),
Expand Down
85 changes: 56 additions & 29 deletions corehq/apps/reports/standard/sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
OUTGOING,
SMS,
WORKFLOWS_FOR_REPORTS,
ConnectMessage,
MessagingEvent,
MessagingSubEvent,
PhoneBlacklist,
Expand Down Expand Up @@ -709,9 +710,9 @@ def get_filters(self):
event_status = EventStatusFilter.get_value(self.request, self.domain)
if event_status == MessagingEvent.STATUS_ERROR:
event_status_filter = (
Q(status=event_status) |
Q(messagingsubevent__status=event_status) |
Q(messagingsubevent__sms__error=True)
Q(status=event_status)
| Q(messagingsubevent__status=event_status)
| Q(messagingsubevent__sms__error=True)
)
elif event_status == MessagingEvent.STATUS_IN_PROGRESS:
# We need to check for id__isnull=False below because the
Expand All @@ -720,22 +721,22 @@ def get_filters(self):
# session_is_open=True if there actually are
# subevent and xforms session records
event_status_filter = (
Q(status=event_status) |
Q(messagingsubevent__status=event_status) |
(Q(messagingsubevent__xforms_session__id__isnull=False) &
Q(messagingsubevent__xforms_session__session_is_open=True))
Q(status=event_status)
| Q(messagingsubevent__status=event_status)
| (Q(messagingsubevent__xforms_session__id__isnull=False)
& Q(messagingsubevent__xforms_session__session_is_open=True))
)
elif event_status == MessagingEvent.STATUS_NOT_COMPLETED:
event_status_filter = (
Q(status=event_status) |
Q(messagingsubevent__status=event_status) |
(Q(messagingsubevent__xforms_session__session_is_open=False) &
Q(messagingsubevent__xforms_session__submission_id__isnull=True))
Q(status=event_status)
| Q(messagingsubevent__status=event_status)
| (Q(messagingsubevent__xforms_session__session_is_open=False)
& Q(messagingsubevent__xforms_session__submission_id__isnull=True))
)
elif event_status == MessagingEvent.STATUS_EMAIL_DELIVERED:
event_status_filter = (
Q(status=event_status) |
Q(messagingsubevent__status=event_status)
Q(status=event_status)
| Q(messagingsubevent__status=event_status)
)

return source_filter, content_type_filter, event_status_filter, error_code_filter
Expand Down Expand Up @@ -773,8 +774,8 @@ def get_queryset(self):

if content_type_filter:
data = data.filter(
(Q(content_type__in=content_type_filter) |
Q(messagingsubevent__content_type__in=content_type_filter))
(Q(content_type__in=content_type_filter)
| Q(messagingsubevent__content_type__in=content_type_filter))
)

if event_status_filter:
Expand Down Expand Up @@ -869,8 +870,13 @@ def template_context(self):
def headers(self):
EMAIL_ADDRRESS = _('Email Address')
PHONE_NUMBER = _('Phone Number')
if self.messaging_event and self.messaging_event.content_type == MessagingEvent.CONTENT_EMAIL:
contact_column = EMAIL_ADDRRESS
CONNECT_ID = _('ConnectID')
if self.messaging_event:
if self.messaging_event.content_type == MessagingEvent.CONTENT_EMAIL:
contact_column = EMAIL_ADDRRESS
elif self.messaging_event.content_type in (MessagingEvent.CONTENT_CONNECT,
MessagingEvent.CONTENT_CONNECT_SURVEY):
contact_column = CONNECT_ID
else:
contact_column = PHONE_NUMBER
return DataTablesHeader(
Expand Down Expand Up @@ -911,11 +917,11 @@ def _fmt_backend_name(self, sms):
def view_response(self):
subevents = self.messaging_subevents
if (
len(subevents) == 1 and
subevents[0].content_type in (MessagingEvent.CONTENT_SMS_SURVEY,
MessagingEvent.CONTENT_IVR_SURVEY) and
subevents[0].xforms_session_id and
subevents[0].status != MessagingEvent.STATUS_ERROR
len(subevents) == 1
and subevents[0].content_type in (MessagingEvent.CONTENT_SMS_SURVEY,
MessagingEvent.CONTENT_IVR_SURVEY)
and subevents[0].xforms_session_id
and subevents[0].status != MessagingEvent.STATUS_ERROR
):
# There's only one survey to report on here - just redirect to the
# survey detail page
Expand Down Expand Up @@ -960,7 +966,8 @@ def rows(self):
self._fmt(status),
])
elif messaging_subevent.content_type in (MessagingEvent.CONTENT_SMS_SURVEY,
MessagingEvent.CONTENT_IVR_SURVEY):
MessagingEvent.CONTENT_IVR_SURVEY,
MessagingEvent.CONTENT_CONNECT_SURVEY):
status = get_status_display(messaging_subevent)
xforms_session = messaging_subevent.xforms_session
timestamp = xforms_session.start_time if xforms_session else messaging_subevent.date
Expand Down Expand Up @@ -994,6 +1001,26 @@ def rows(self):
self._fmt(_('Email')),
self._fmt(status),
])
elif messaging_subevent.content_type == MessagingEvent.CONTENT_CONNECT:
timestamp = ServerTime(messaging_subevent.date).user_time(self.timezone).done()
status = get_status_display(messaging_subevent)
content = '-'
recipient_address = '-'
try:
msg = ConnectMessage.objects.get(messaging_subevent=messaging_subevent.pk)
except ConnectMessage.DoesNotExist:
pass
content = msg.text
recipient = msg.couch_recipient
result.append([
self._fmt_timestamp(timestamp),
self._fmt_contact_link(messaging_subevent.recipient_id, doc_info),
self._fmt(content),
self._fmt(recipient),
self._fmt_direction(msg.direction),
self._fmt(_('Connect Message')),
self._fmt(status),
])
return result


Expand Down Expand Up @@ -1205,9 +1232,9 @@ def verification_status(self):
@property
def _show_users_without_phone_numbers(self):
return (
self.filter_type == 'contact' and
self.contact_type == 'users' and
self.has_phone_number != 'has_phone_number'
self.filter_type == 'contact'
and self.contact_type == 'users'
and self.has_phone_number != 'has_phone_number'
)

@property
Expand All @@ -1229,9 +1256,9 @@ def _fmt_status(self, number):
elif number.pending_verification:
return "Verification Pending"
elif (
not (number.is_two_way or number.pending_verification) and
PhoneNumber.get_reserved_number(number.phone_number)
):
not (number.is_two_way or number.pending_verification)
and PhoneNumber.get_reserved_number(number.phone_number)
):
return "Already In Use"
return "Not Verified"

Expand Down
Loading
Loading