def save_history(self, message_content, participant_phone, message_type, message_status=None, message_id=None, failure_reason=None, timestamp=None, reference_metadata=None): if timestamp: timestamp = time_to_vusion_format(timestamp) else: timestamp = time_to_vusion_format(self.get_local_time()) history = { 'message-id': message_id, 'message-content': message_content, 'participant-phone': participant_phone, 'message-type': message_type, 'message-status': message_status, 'timestamp': timestamp, } if failure_reason is not None: history['failure-reason'] = failure_reason if reference_metadata is None: reference_metadata = {} for key, value in reference_metadata.iteritems(): history[key] = value self.collections['history'].save(history)
def test05_send_scheduled_messages(self): dialogue = self.dialogue_annoucement_2 participant = {'phone': '06'} mytimezone = self.program_settings[2]['value'] dNow = datetime.utcnow().replace(tzinfo=pytz.utc).astimezone(pytz.timezone(mytimezone)) dNow = dNow - timedelta(minutes=1) dPast = dNow - timedelta(minutes=30) dFuture = dNow + timedelta(minutes=30) self.collections['dialogues'].save(dialogue) self.collections['participants'].save(participant) for program_setting in self.program_settings: self.collections['program_settings'].save(program_setting) unattached_message = self.collections['unattached_messages'].save({ 'date-time': time_to_vusion_format(dNow), 'content': 'Hello unattached', 'to': 'all participants', 'type-interaction': 'annoucement' }) self.collections['schedules'].save({ 'date-time': dPast.strftime(self.time_format), 'dialogue-id': '2', 'interaction-id': '0', 'participant-phone': '09'}) self.collections['schedules'].save({ 'date-time': dNow.strftime(self.time_format), 'dialogue-id': '2', 'interaction-id': '1', 'participant-phone': '09'}) self.collections['schedules'].save({ 'date-time': dFuture.strftime(self.time_format), 'dialogue-id': '2', 'interaction-id': '2', 'participant-phone': '09'}) self.collections['schedules'].save({ 'date-time': time_to_vusion_format(dNow), 'unattach-id': unattached_message, 'participant-phone': '09' }) self.collections['schedules'].save({ 'date-time': time_to_vusion_format(dNow), 'type-content': 'feedback', 'content': 'Thank you', 'participant-phone': '09' }) self.worker.load_data() yield self.worker.send_scheduled() messages = self.broker.get_messages('vumi', 'test.outbound') self.assertEqual(len(messages), 3) self.assertEqual(messages[0]['content'], 'Today will be sunny') self.assertEqual(messages[1]['content'], 'Hello unattached') self.assertEqual(messages[2]['content'], 'Thank you') self.assertEquals(self.collections['schedules'].count(), 1) self.assertEquals(self.collections['history'].count(), 4)
def consume_user_message(self, msg): regex_KEYWORD = re.compile('KEYWORD') log.debug("Consumer user message %s" % (msg,)) if msg['timestamp']: timestamp = time_to_vusion_format(msg['timestamp']) self.unmatchable_reply_collection.save( {'participant-phone': msg['from_addr'], 'to': msg['to_addr'], 'message-content': msg['content'], 'timestamp': timestamp, }) code = self.shortcodes_collection.find_one({ 'shortcode': msg['to_addr']}) if code is None: return template = self.templates_collection.find_one({ '_id': ObjectId(code['error-template'])}) if template is None: return error_message = TransportUserMessage(**{ 'from_addr': msg['to_addr'], 'to_addr': msg['from_addr'], 'transport_name': msg['transport_name'], 'transport_type': msg['transport_type'], 'transport_metadata': msg['transport_metadata'], 'content': re.sub( regex_KEYWORD, get_first_word(msg['content']), template['template'] ) }) yield self.transport_publisher.publish_message(error_message) log.debug("Reply '%s' sent to %s" % (error_message['content'], error_message['to_addr']))
def test21_schedule_unattach_message(self): participants = [{'phone': '06'}, {'phone': '07'}] mytimezone = self.program_settings[2]['value'] dNow = datetime.utcnow().replace(tzinfo=pytz.utc).astimezone(pytz.timezone(mytimezone)) dFuture = dNow + timedelta(minutes=30) dPast = dNow - timedelta(minutes=30) unattach_messages = [ { 'to': 'all participants', 'content': 'Hello everyone', 'schedule': 'fixed-time', 'fixed-time': time_to_vusion_format(dFuture)}, { 'to': 'all participants', 'content': 'Hello again', 'schedule': 'fixed-time', 'fixed-time': time_to_vusion_format(dPast)}] for program_setting in self.program_settings: self.collections['program_settings'].save(program_setting) for participant in participants: self.collections['participants'].save(participant) unattach_id = self.collections['unattached_messages'].save(unattach_messages[0]) self.collections['unattached_messages'].save(unattach_messages[1]) self.collections['history'].save({ 'participant-phone': '06', 'message-type': 'sent', 'message-status': 'delivered', 'unattach-id': unattach_id }) self.worker.load_data() self.worker.schedule_participants_unattach_messages( participants) schedules_count = self.collections['schedules'].count() self.assertEqual(schedules_count, 1) schedules = self.collections['schedules'].find() self.assertEqual(schedules[0]['participant-phone'], '07')
def test21_schedule_unattach_message(self): participants = [{'phone': '06'}, {'phone': '07'}] mytimezone = self.program_settings[2]['value'] dNow = datetime.utcnow().replace(tzinfo=pytz.utc).astimezone( pytz.timezone(mytimezone)) dFuture = dNow + timedelta(minutes=30) dPast = dNow - timedelta(minutes=30) unattach_messages = [{ 'to': 'all participants', 'content': 'Hello everyone', 'schedule': 'fixed-time', 'fixed-time': time_to_vusion_format(dFuture) }, { 'to': 'all participants', 'content': 'Hello again', 'schedule': 'fixed-time', 'fixed-time': time_to_vusion_format(dPast) }] for program_setting in self.program_settings: self.collections['program_settings'].save(program_setting) for participant in participants: self.collections['participants'].save(participant) unattach_id = self.collections['unattached_messages'].save( unattach_messages[0]) self.collections['unattached_messages'].save(unattach_messages[1]) self.collections['history'].save({ 'participant-phone': '06', 'message-type': 'sent', 'message-status': 'delivered', 'unattach-id': unattach_id }) self.worker.load_data() self.worker.schedule_participants_unattach_messages(participants) schedules_count = self.collections['schedules'].count() self.assertEqual(schedules_count, 1) schedules = self.collections['schedules'].find() self.assertEqual(schedules[0]['participant-phone'], '07')
def log(self, msg, level='msg'): timezone = None local_time = self.get_local_time() rkey = "%slogs" % (self.r_prefix, ) self.r_server.zremrangebyscore( rkey, 1, get_local_time_as_timestamp(local_time - timedelta(hours=2))) self.r_server.zadd( rkey, "[%s] %s" % (time_to_vusion_format(local_time), msg), get_local_time_as_timestamp(local_time)) if (level == 'msg'): log.msg('[%s] %s' % (self.control_name, msg)) else: log.error('[%s] %s' % (self.control_name, msg))
def log(self, msg, level='msg'): timezone = None local_time = self.get_local_time() rkey = "%slogs" % (self.r_prefix,) self.r_server.zremrangebyscore( rkey, 1, get_local_time_as_timestamp( local_time - timedelta(hours=2)) ) self.r_server.zadd( rkey, "[%s] %s" % ( time_to_vusion_format(local_time), msg), get_local_time_as_timestamp(local_time)) if (level == 'msg'): log.msg('[%s] %s' % (self.control_name, msg)) else: log.error('[%s] %s' % (self.control_name, msg))
def send_scheduled(self): self.log('Checking the schedule list...') local_time = self.get_local_time() toSends = self.collections['schedules'].find( spec={'date-time': {'$lt': time_to_vusion_format(local_time)}}, sort=[('date-time', 1)]) for toSend in toSends: self.collections['schedules'].remove( {'_id': toSend['_id']}) message_content = None try: interaction, reference_metadata = self.from_schedule_to_message(toSend) if not interaction: continue message_content = self.generate_message(interaction) message_content = self.customize_message( toSend['participant-phone'], message_content) if (time_from_vusion_format(toSend['date-time']) < (local_time - timedelta(minutes=15))): raise SendingDatePassed( "Message should have been send at %s" % (toSend['date-time'],)) message = TransportUserMessage(**{ 'from_addr': self.properties['shortcode'], 'to_addr': toSend['participant-phone'], 'transport_name': self.transport_name, 'transport_type': self.transport_type, 'transport_metadata': '', 'content': message_content}) yield self.transport_publisher.publish_message(message) self.log( "Message has been send to %s '%s'" % (message['to_addr'], message['content'])) self.save_history( message_content=message['content'], participant_phone=message['to_addr'], message_type='sent', message_status='pending', message_id=message['message_id'], reference_metadata=reference_metadata) except VusionError as e: self.save_history( message_content='', participant_phone=toSend['participant-phone'], message_type=None, failure_reason=('%s' % (e,)), reference_metadata=reference_metadata) except: exc_type, exc_value, exc_traceback = sys.exc_info() self.log( "Error during consume user message: %r" % traceback.format_exception(exc_type, exc_value, exc_traceback)) self.save_history( participant_phone=toSend['participant-phone'], message_content='', message_type='system-failed', failure_reason=('%s - %s') % (sys.exc_info()[0], sys.exc_info()[1]), reference_metadata=reference_metadata)
def get_future_unattach_messages(self): return self.collections['unattached_messages'].find({ 'fixed-time': { '$gt': time_to_vusion_format(self.get_local_time()) }})
def run_action(self, participant_phone, action): regex_ANSWER = re.compile('ANSWER') if (action['type-action'] == 'optin'): self.collections['participants'].update( {'phone': participant_phone}, {'$unset': {'optout': 1}}, True) elif (action['type-action'] == 'optout'): self.collections['participants'].update( {'phone': participant_phone}, {'$set': {'optout': True}}) elif (action['type-action'] == 'feedback'): self.collections['schedules'].save({ 'date-time': time_to_vusion_format(self.get_local_time()), 'content': action['content'], 'type-content': 'feedback', 'participant-phone': participant_phone }) elif (action['type-action'] == 'unmatching-answer'): setting = self.collections['program_settings'].find_one({ 'key': 'default-template-unmatching-answer'}) if setting is None: return template = self.collections['templates'].find_one({ '_id': ObjectId(setting['value'])}) if template is None: return error_message = TransportUserMessage(**{ 'from_addr': '8282', 'to_addr': participant_phone, 'transport_name': None, 'transport_type': None, 'transport_metadata': None, 'content': re.sub(regex_ANSWER, action['answer'], template['template']) }) self.collections['schedules'].save({ 'date-time': time_to_vusion_format(self.get_local_time()), 'content': error_message['content'], 'type-content': 'feedback', 'participant-phone': participant_phone }) log.debug("Reply '%s' sent to %s" % (error_message['content'], error_message['to_addr'])) elif (action['type-action'] == 'tagging'): self.collections['participants'].update( {'phone': participant_phone, 'tags': {'$ne': action['tag']}}, {'$push': {'tags': action['tag']}}) elif (action['type-action'] == 'enrolling'): self.collections['participants'].update( {'phone': participant_phone, 'enrolled': {'$ne': action['enroll']}}, {'$push': {'enrolled': action['enroll']}}, True) dialogue = self.get_current_dialogue(action['enroll']) participant = self.collections['participants'].find_one( {'phone': participant_phone}) self.schedule_participant_dialogue(participant, dialogue) elif (action['type-action'] == 'profiling'): self.collections['participants'].update( {'phone': participant_phone}, {'$set': {action['label']: action['value']}}) else: self.log("The action is not supported %s" % action['type-action'])
def send_scheduled(self): self.log('Checking the schedule list...') local_time = self.get_local_time() toSends = self.collections['schedules'].find( spec={'date-time': { '$lt': time_to_vusion_format(local_time) }}, sort=[('date-time', 1)]) for toSend in toSends: self.collections['schedules'].remove({'_id': toSend['_id']}) message_content = None try: interaction, reference_metadata = self.from_schedule_to_message( toSend) if not interaction: continue message_content = self.generate_message(interaction) message_content = self.customize_message( toSend['participant-phone'], message_content) if (time_from_vusion_format(toSend['date-time']) < (local_time - timedelta(minutes=15))): raise SendingDatePassed( "Message should have been send at %s" % (toSend['date-time'], )) message = TransportUserMessage( **{ 'from_addr': self.properties['shortcode'], 'to_addr': toSend['participant-phone'], 'transport_name': self.transport_name, 'transport_type': self.transport_type, 'transport_metadata': '', 'content': message_content }) yield self.transport_publisher.publish_message(message) self.log("Message has been send to %s '%s'" % (message['to_addr'], message['content'])) self.save_history(message_content=message['content'], participant_phone=message['to_addr'], message_type='sent', message_status='pending', message_id=message['message_id'], reference_metadata=reference_metadata) except VusionError as e: self.save_history( message_content='', participant_phone=toSend['participant-phone'], message_type=None, failure_reason=('%s' % (e, )), reference_metadata=reference_metadata) except: exc_type, exc_value, exc_traceback = sys.exc_info() self.log("Error during consume user message: %r" % traceback.format_exception(exc_type, exc_value, exc_traceback)) self.save_history( participant_phone=toSend['participant-phone'], message_content='', message_type='system-failed', failure_reason=('%s - %s') % (sys.exc_info()[0], sys.exc_info()[1]), reference_metadata=reference_metadata)
def get_future_unattach_messages(self): return self.collections['unattached_messages'].find({ 'fixed-time': { '$gt': time_to_vusion_format(self.get_local_time()) } })
def run_action(self, participant_phone, action): regex_ANSWER = re.compile('ANSWER') if (action['type-action'] == 'optin'): self.collections['participants'].update( {'phone': participant_phone}, {'$unset': { 'optout': 1 }}, True) elif (action['type-action'] == 'optout'): self.collections['participants'].update( {'phone': participant_phone}, {'$set': { 'optout': True }}) elif (action['type-action'] == 'feedback'): self.collections['schedules'].save({ 'date-time': time_to_vusion_format(self.get_local_time()), 'content': action['content'], 'type-content': 'feedback', 'participant-phone': participant_phone }) elif (action['type-action'] == 'unmatching-answer'): setting = self.collections['program_settings'].find_one( {'key': 'default-template-unmatching-answer'}) if setting is None: return template = self.collections['templates'].find_one( {'_id': ObjectId(setting['value'])}) if template is None: return error_message = TransportUserMessage( **{ 'from_addr': '8282', 'to_addr': participant_phone, 'transport_name': None, 'transport_type': None, 'transport_metadata': None, 'content': re.sub(regex_ANSWER, action['answer'], template['template']) }) self.collections['schedules'].save({ 'date-time': time_to_vusion_format(self.get_local_time()), 'content': error_message['content'], 'type-content': 'feedback', 'participant-phone': participant_phone }) log.debug("Reply '%s' sent to %s" % (error_message['content'], error_message['to_addr'])) elif (action['type-action'] == 'tagging'): self.collections['participants'].update( { 'phone': participant_phone, 'tags': { '$ne': action['tag'] } }, {'$push': { 'tags': action['tag'] }}) elif (action['type-action'] == 'enrolling'): self.collections['participants'].update( { 'phone': participant_phone, 'enrolled': { '$ne': action['enroll'] } }, {'$push': { 'enrolled': action['enroll'] }}, True) dialogue = self.get_current_dialogue(action['enroll']) participant = self.collections['participants'].find_one( {'phone': participant_phone}) self.schedule_participant_dialogue(participant, dialogue) elif (action['type-action'] == 'profiling'): self.collections['participants'].update( {'phone': participant_phone}, {'$set': { action['label']: action['value'] }}) else: self.log("The action is not supported %s" % action['type-action'])
def test05_send_scheduled_messages(self): dialogue = self.dialogue_annoucement_2 participant = {'phone': '06'} mytimezone = self.program_settings[2]['value'] dNow = datetime.utcnow().replace(tzinfo=pytz.utc).astimezone( pytz.timezone(mytimezone)) dNow = dNow - timedelta(minutes=1) dPast = dNow - timedelta(minutes=30) dFuture = dNow + timedelta(minutes=30) self.collections['dialogues'].save(dialogue) self.collections['participants'].save(participant) for program_setting in self.program_settings: self.collections['program_settings'].save(program_setting) unattached_message = self.collections['unattached_messages'].save({ 'date-time': time_to_vusion_format(dNow), 'content': 'Hello unattached', 'to': 'all participants', 'type-interaction': 'annoucement' }) self.collections['schedules'].save({ 'date-time': dPast.strftime(self.time_format), 'dialogue-id': '2', 'interaction-id': '0', 'participant-phone': '09' }) self.collections['schedules'].save({ 'date-time': dNow.strftime(self.time_format), 'dialogue-id': '2', 'interaction-id': '1', 'participant-phone': '09' }) self.collections['schedules'].save({ 'date-time': dFuture.strftime(self.time_format), 'dialogue-id': '2', 'interaction-id': '2', 'participant-phone': '09' }) self.collections['schedules'].save({ 'date-time': time_to_vusion_format(dNow), 'unattach-id': unattached_message, 'participant-phone': '09' }) self.collections['schedules'].save({ 'date-time': time_to_vusion_format(dNow), 'type-content': 'feedback', 'content': 'Thank you', 'participant-phone': '09' }) self.worker.load_data() yield self.worker.send_scheduled() messages = self.broker.get_messages('vumi', 'test.outbound') self.assertEqual(len(messages), 3) self.assertEqual(messages[0]['content'], 'Today will be sunny') self.assertEqual(messages[1]['content'], 'Hello unattached') self.assertEqual(messages[2]['content'], 'Thank you') self.assertEquals(self.collections['schedules'].count(), 1) self.assertEquals(self.collections['history'].count(), 4)