Exemple #1
0
 def test_track_without_tracking_context(self):
     segment.track(sentinel.user_id, sentinel.name, self.properties)
     self.assertTrue(self.mock_segment_track.called)
     args, kwargs = self.mock_segment_track.call_args
     expected_segment_context = {}
     self.assertEqual((sentinel.user_id, sentinel.name, self.properties,
                       expected_segment_context), args)
Exemple #2
0
def _track_user_registration(user, profile, params, third_party_provider):
    """ Track the user's registration. """
    if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
        identity_args = [
            user.id,
            {
                'email': user.email,
                'username': user.username,
                'name': profile.name,
                # Mailchimp requires the age & yearOfBirth to be integers, we send a sane integer default if falsey.
                'age': profile.age or -1,
                'yearOfBirth': profile.year_of_birth or datetime.datetime.now(UTC).year,
                'education': profile.level_of_education_display,
                'address': profile.mailing_address,
                'gender': profile.gender_display,
                'country': text_type(profile.country),
            }
        ]
        # .. pii: Many pieces of PII are sent to Segment here. Retired directly through Segment API call in Tubular.
        # .. pii_types: email_address, username, name, birth_date, location, gender
        # .. pii_retirement: third_party
        segment.identify(*identity_args)
        segment.track(
            user.id,
            "edx.bi.user.account.registered",
            {
                'category': 'conversion',
                # ..pii: Learner email is sent to Segment in following line and will be associated with analytics data.
                'email': user.email,
                'label': params.get('course_id'),
                'provider': third_party_provider.name if third_party_provider else None
            },
        )
def _is_in_holdback_and_bucket(user):
    """
    Return whether the specified user is in the first-purchase-discount holdback group.
    This will also stable bucket the user.
    """
    if datetime(2020, 8, 1, tzinfo=pytz.UTC) <= datetime.now(tz=pytz.UTC):
        return False

    # Holdback is 10%
    bucket = stable_bucketing_hash_group(DISCOUNT_APPLICABILITY_HOLDBACK, 10,
                                         user)

    request = get_current_request()
    if hasattr(request, 'session'
               ) and DISCOUNT_APPLICABILITY_HOLDBACK not in request.session:
        properties = {
            'site': request.site.domain,
            'app_label': 'discounts',
            'nonInteraction': 1,
            'bucket': bucket,
            'experiment': 'REVEM-363',
        }
        segment.track(
            user_id=user.id,
            event_name='edx.bi.experiment.user.bucketed',
            properties=properties,
        )

        # Mark that we've recorded this bucketing, so that we don't do it again this session
        request.session[DISCOUNT_APPLICABILITY_HOLDBACK] = True

    return bucket == 0
Exemple #4
0
def _track_message_sent(site, user, msg):  # lint-amnesty, pylint: disable=missing-function-docstring
    properties = {
        'site': site.domain,
        'app_label': msg.app_label,
        'name': msg.name,
        'language': msg.language,
        'uuid': six.text_type(msg.uuid),
        'send_uuid': six.text_type(msg.send_uuid),
        'nonInteraction': 1,
    }
    course_ids = msg.context.get('course_ids', [])
    properties['num_courses'] = len(course_ids)
    if len(course_ids) > 0:
        properties['course_ids'] = course_ids[:10]
        properties['primary_course_id'] = course_ids[0]

    tracking_context = {
        'host': site.domain,
        'path':
        '/',  # make up a value, in order to allow the host to be passed along.
    }
    # I wonder if the user of this event should be the recipient, as they are not the ones
    # who took an action.  Rather, the system is acting, and they are the object.
    # Admittedly that may be what 'nonInteraction' is meant to address.  But sessionization may
    # get confused by these events if they're attributed in this way, because there's no way for
    # this event to get context that would match with what the user might be doing at the moment.
    # But the events do show up in GA being joined up with existing sessions (i.e. within a half
    # hour in the past), so they don't always break sessions.  Not sure what happens after these.
    # We can put the recipient_user_id into the properties, and then export as a custom dimension.
    with tracker.get_tracker().context(msg.app_label, tracking_context):
        segment.track(
            user_id=user.id,
            event_name='edx.bi.email.sent',
            properties=properties,
        )
Exemple #5
0
def _should_randomly_suppress_schedule_creation(
    schedule_config,
    enrollment,
    upgrade_deadline,
    experience_type,
    content_availability_date,
):
    # The hold back ratio is always between 0 and 1. A value of 0 indicates that schedules should be created for all
    # schedules. A value of 1 indicates that no schedules should be created for any enrollments. A value of 0.2 would
    # mean that 20% of enrollments should *not* be given schedules.

    # This allows us to measure the impact of the dynamic schedule experience by comparing this "control" group that
    # does not receive any of benefits of the feature against the group that does.
    if random.random() < schedule_config.hold_back_ratio:
        log.debug(
            'Schedules: Enrollment held back from dynamic schedule experiences.'
        )
        upgrade_deadline_str = None
        if upgrade_deadline:
            upgrade_deadline_str = upgrade_deadline.isoformat()
        segment.track(user_id=enrollment.user.id,
                      event_name='edx.bi.schedule.suppressed',
                      properties={
                          'course_id':
                          six.text_type(enrollment.course_id),
                          'experience_type':
                          experience_type,
                          'upgrade_deadline':
                          upgrade_deadline_str,
                          'content_availability_date':
                          content_availability_date.isoformat(),
                      })
        return True

    return False
Exemple #6
0
def _track_notification_sent(message, context):
    """
    Send analytics event for a sent email
    """
    properties = {
        'app_label': 'discussion',
        'name': 'responsenotification',  # This is 'Campaign' in GA
        'language': message.language,
        'uuid': str(message.uuid),
        'send_uuid': str(message.send_uuid),
        'thread_id': context['thread_id'],
        'course_id': str(context['course_id']),
        'thread_created_at': date.deserialize(context['thread_created_at']),
        'nonInteraction': 1,
    }
    tracking_context = {
        'host': context['site'].domain,
        'path':
        '/',  # make up a value, in order to allow the host to be passed along.
    }
    # The event used to specify the user_id as being the recipient of the email (i.e. the thread_author_id).
    # This has the effect of interrupting the actual chain of events for that author, if any, while the
    # email-sent event should really be associated with the sender, since that is what triggers the event.
    with tracker.get_tracker().context(properties['app_label'],
                                       tracking_context):
        segment.track(user_id=context['thread_author_id'],
                      event_name='edx.bi.email.sent',
                      properties=properties)
 def test_track_without_tracking_context(self):
     segment.track(sentinel.user_id, sentinel.name, self.properties)
     self.assertTrue(self.mock_segment_track.called)
     args, kwargs = self.mock_segment_track.call_args  # lint-amnesty, pylint: disable=unused-variable
     expected_segment_context = {}
     self.assertEqual((sentinel.user_id, sentinel.name, self.properties,
                       expected_segment_context), args)
Exemple #8
0
def _track_user_login(user, request):
    """
    Sends a tracking event for a successful login.
    """
    # .. pii: Username and email are sent to Segment here. Retired directly through Segment API call in Tubular.
    # .. pii_types: email_address, username
    # .. pii_retirement: third_party
    segment.identify(
        user.id,
        {
            'email': request.POST.get('email'),
            'username': user.username
        },
        {
            # Disable MailChimp because we don't want to update the user's email
            # and username in MailChimp on every page load. We only need to capture
            # this data on registration/activation.
            'MailChimp': False
        })
    segment.track(
        user.id,
        "edx.bi.user.account.authenticated",
        {
            'category': "conversion",
            'label': request.POST.get('course_id'),
            'provider': None
        },
    )
def emit_course_goal_event(sender, instance, **kwargs):  # lint-amnesty, pylint: disable=unused-argument
    """Emit events for both tracking logs and for Segment."""
    name = 'edx.course.goal.added' if kwargs.get(
        'created', False) else 'edx.course.goal.updated'
    tracker.emit(name, {
        'goal_key': instance.goal_key,
    })
    segment.track(instance.user.id, name)
Exemple #10
0
def emit_webinar_registration_event(user_id, topic):
    """
    Emit an event for a user's webinar registration

    Arguments:
        user_id (id): Id of the registering user
        topic (str): Topic of the registered webinar
    """
    segment.track(user_id, WEBINAR_REGISTRATION_EVENT, {'topic': topic})
Exemple #11
0
def _track_user_registration(user, profile, params, third_party_provider, registration):
    """ Track the user's registration. """
    if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
        identity_args = [
            user.id,
            {
                'email': user.email,
                'username': user.username,
                'name': profile.name,
                # Mailchimp requires the age & yearOfBirth to be integers, we send a sane integer default if falsey.
                'age': profile.age or -1,
                'yearOfBirth': profile.year_of_birth or datetime.datetime.now(UTC).year,
                'education': profile.level_of_education_display,
                'address': profile.mailing_address,
                'gender': profile.gender_display,
                'country': str(profile.country),
                'email_subscribe': 'unsubscribed' if settings.MARKETING_EMAILS_OPT_IN and
                                   params.get('marketing_emails_opt_in') == 'false' else 'subscribed',
            }
        ]
        # .. pii: Many pieces of PII are sent to Segment here. Retired directly through Segment API call in Tubular.
        # .. pii_types: email_address, username, name, birth_date, location, gender
        # .. pii_retirement: third_party
        segment.identify(*identity_args)
        properties = {
            'category': 'conversion',
            # ..pii: Learner email is sent to Segment in following line and will be associated with analytics data.
            'email': user.email,
            'label': params.get('course_id'),
            'provider': third_party_provider.name if third_party_provider else None,
            'is_gender_selected': bool(profile.gender_display),
            'is_year_of_birth_selected': bool(profile.year_of_birth),
            'is_education_selected': bool(profile.level_of_education_display),
            'is_goal_set': bool(profile.goals),
            'total_registration_time': round(float(params.get('totalRegistrationTime', '0'))),
            'activation_key': registration.activation_key if registration else None,
        }
        # VAN-738 - added below properties to experiment marketing emails opt in/out events on Braze.
        if params.get('marketing_emails_opt_in') and settings.MARKETING_EMAILS_OPT_IN:
            properties['marketing_emails_opt_in'] = params.get('marketing_emails_opt_in') == 'true'

        # DENG-803: For segment events forwarded along to Hubspot, duplicate the `properties` section of
        # the event payload into the `traits` section so that they can be received. This is a temporary
        # fix until we implement this behavior outside of the LMS.
        # TODO: DENG-805: remove the properties duplication in the event traits.
        segment_traits = dict(properties)
        segment_traits['user_id'] = user.id
        segment_traits['joined_date'] = user.date_joined.strftime("%Y-%m-%d")
        segment.track(
            user.id,
            "edx.bi.user.account.registered",
            properties=properties,
            traits=segment_traits,
        )
Exemple #12
0
def emit_course_goal_event(sender, instance, **kwargs):  # lint-amnesty, pylint: disable=unused-argument
    """Emit events for both tracking logs and for Segment."""
    name = 'edx.course.goal.added' if kwargs.get(
        'created', False) else 'edx.course.goal.updated'
    properties = {
        'courserun_key': str(instance.course_key),
        'days_per_week': instance.days_per_week,
        'subscribed_to_reminders': instance.subscribed_to_reminders,
    }
    tracker.emit(name, properties)
    segment.track(instance.user.id, name, properties)
Exemple #13
0
    def _fire_event(self, user, event_name, parameters):
        """
        Fire an analytics event.

        Arguments:
            user (User): The user who submitted photos.
            event_name (str): Name of the analytics event.
            parameters (dict): Event parameters.

        Returns: None

        """
        segment.track(user.id, event_name, parameters)
Exemple #14
0
def emit_registration_event(user):
    """
    Track and Identify required attributes when a new user is created

    Arguments:
        user (User): User to track
    """
    user_properties = {
        'email': user.email,
        'username': user.username,
        'name': user.profile.name,
        'city': user.profile.city,
    }
    segment.identify(user.id, user_properties)
    segment.track(user.id, USER_REGISTRATION_EVENT, user_properties)
Exemple #15
0
def login_analytics(strategy, auth_entry, current_partial=None, *args, **kwargs):  # lint-amnesty, pylint: disable=keyword-arg-before-vararg
    """ Sends login info to Segment """

    event_name = None
    if auth_entry == AUTH_ENTRY_LOGIN:
        event_name = 'edx.bi.user.account.authenticated'
    elif auth_entry in [AUTH_ENTRY_ACCOUNT_SETTINGS]:
        event_name = 'edx.bi.user.account.linked'

    if event_name is not None:
        segment.track(kwargs['user'].id, event_name, {
            'category': "conversion",
            'label': None,
            'provider': kwargs['backend'].name
        })
Exemple #16
0
def emit_course_status_event_for_program_prereq_courses(user_id, course, course_status):
    """
    Emit the progress status for Program prereq courses i.e Started or Completed

    Arguments:
        user_id (id): Id of the user for which to emit the Omni course progress update
        course (CourseOverview): Course for which to emit the event for
        course_status (str): The status of the given course
    """
    course_name_status_key = f'{course.id.course}Status'
    segment.identify(user_id, {course_name_status_key: course_status})

    course_update_event_name = f'ProgramPrereq{course_status}'
    segment.track(user_id, course_update_event_name, {
        'course': course.id.course,
        'label': text_type(course.id),
        'org': course.id.org,
        'run': course.id.run,
    })
Exemple #17
0
def get_celebrations_dict(user, enrollment, course, browser_timezone):
    """
    Returns a dict of celebrations that should be performed.
    """
    if not enrollment:
        return {
            'first_section': False,
            'streak_length_to_celebrate': None,
            'streak_discount_enabled': False,
        }

    streak_length_to_celebrate = UserCelebration.perform_streak_updates(
        user, course.id, browser_timezone)
    celebrations = {
        'first_section':
        CourseEnrollmentCelebration.should_celebrate_first_section(enrollment),
        'streak_length_to_celebrate':
        streak_length_to_celebrate,
        'streak_discount_enabled':
        False,
    }

    if streak_length_to_celebrate:
        # We only want to offer the streak discount
        # if the course has not ended, is upgradeable and the user is not an enterprise learner

        if can_show_streak_discount_coupon(user, course):
            # Send course streak coupon event
            course_key = str(course.id)
            modes_dict = CourseMode.modes_for_course_dict(
                course_id=course_key, include_expired=False)
            verified_mode = modes_dict.get('verified', None)
            if verified_mode:
                celebrations['streak_discount_enabled'] = True
                segment.track(
                    user_id=user.id,
                    event_name='edx.bi.course.streak_discount_enabled',
                    properties={
                        'course_id': str(course_key),
                        'sku': verified_mode.sku,
                    })

    return celebrations
Exemple #18
0
def emit_user_info_update(user, application_step):
    """
    Emit an event to update the user information at Segment after different steps of the application.

    Arguments:
        user (User): User for which to emit the event
        application_step (str): Step of the application to update the user information for
    """
    if application_step == CONTACT_INFO:
        attributes = {
            'isSaudi': user.extended_profile.saudi_national,
            'age': user.extended_profile.age,
            'heardAboutUs': user.extended_profile.hear_about_omni,
        }

    elif application_step == EXPERIENCE:
        first_education = user.application.educations.first()
        education_start_date, education_end_date = _get_start_date_and_end_date(first_education)
        attributes = {
            'school': first_education.name_of_school,
            'degree': first_education.get_degree_display(),
            'educationStartDate': education_start_date,
            'educationEndDate': education_end_date,
        }

        first_experience = user.application.workexperiences.first()
        if first_experience:
            experience_start_date, experience_end_date = _get_start_date_and_end_date(first_experience)
            attributes.update({
                'workOrganization': first_experience.name_of_organization,
                'jobTitle': first_experience.job_position_title,
                'workStartDate': experience_start_date,
                'workEndDate': experience_end_date
            })

    else:
        attributes = {
            'businessLine': user.application.business_line.title
        }

    segment.identify(user.id, attributes)
    segment.track(user.id, APPLICATION_EVENTS.get(application_step), attributes)
Exemple #19
0
def check_pwned_password_and_send_track_event(user_id,
                                              password,
                                              internal_user=False):
    """
    Check the Pwned Databases and send its event to Segment
    """
    try:
        password = hashlib.sha1(password.encode('utf-8')).hexdigest()
        pwned_response = PwnedPasswordsAPI.range(password)
        if pwned_response is not None:
            properties = get_pwned_properties(pwned_response, password)
            properties['internal_user'] = internal_user
            segment.track(user_id, 'edx.bi.user.pwned.password.status',
                          properties)
    except Exception:  # pylint: disable=W0703
        log.exception(
            'Unable to get response from pwned password api for user_id: "%s"',
            user_id,
        )
        return None  # lint-amnesty, pylint: disable=raise-missing-from
Exemple #20
0
def check_pwned_password_and_send_track_event(user_id,
                                              password,
                                              internal_user=False,
                                              is_new_user=False):
    """
    Check the Pwned Databases and send its event to Segment.
    """
    try:
        pwned_properties = check_pwned_password(password)
        if pwned_properties:
            pwned_properties['internal_user'] = internal_user
            pwned_properties['new_user'] = is_new_user
            segment.track(user_id, 'edx.bi.user.pwned.password.status',
                          pwned_properties)
    except Exception:  # pylint: disable=W0703
        log.exception(
            'Unable to get response from pwned password api for user_id: "%s"',
            user_id,
        )
        return None  # lint-amnesty, pylint: disable=raise-missing-from
Exemple #21
0
def emit_application_progress(user):
    """
    Emit the progress for the application of the given user. The progress statuses can be Started, Submitted,
    Waitlisted and Accepted.

    Arguments:
        user (User): User to emit progress update for
    """
    application_progress = user.application_hub.application_progress

    application_status = APPLICATION_PROGRESS.get(application_progress, APPLICATION_PROGRESS[APPLICATION_STARTED])
    segment.identify(user.id, {'applicationStatus': application_status})

    application_event = APPLICATION_EVENTS.get(application_progress, APPLICATION_EVENTS[APPLICATION_STARTED])
    event_properties = {
        'applicationStatus': application_status
    }
    if hasattr(user, 'application'):
        event_properties['applicationId'] = user.application.id
    segment.track(user.id, application_event, event_properties)
Exemple #22
0
def _track_update_email_opt_in(user_id, organization, opt_in):
    """Track an email opt-in preference change.

    Arguments:
        user_id (str): The ID of the user making the preference change.
        organization (str): The organization whose emails are being opted into or out of by the user.
        opt_in (bool): Whether the user has chosen to opt-in to emails from the organization.

    Returns:
        None

    """
    event_name = 'edx.bi.user.org_email.opted_in' if opt_in else 'edx.bi.user.org_email.opted_out'
    segment.track(
        user_id,
        event_name,
        {
            'category': 'communication',
            'label': organization
        },
    )
Exemple #23
0
def emit_course_progress_event(user, course, grade=None):
    """
    Emit the progress of a course when a course is Started, Completed or Failed. Also calls
    `emit_course_status_event_for_program_prereq_courses` function to emit an event if a program
    prereq course is started or passed.

    Arguments:
        user (User): User associated with the course
        course (CourseOverview): The course for which we are emitting the progress
        grade (PersistentCourseGrade): The grade for the given course and user
    """
    from openedx.adg.lms.applications.helpers import has_attempted_all_modules

    is_program_prereq_course = (
        hasattr(course, 'multilingual_course') and
        course.multilingual_course.multilingual_course_group.is_program_prerequisite
    )

    course_language_name = get_language_info(course.language).get('name', course.language)
    course_properties = {'courseName': course.id.course, 'courseLanguage': course_language_name}

    if grade:
        course_properties['letterGrade'] = text_type(grade.letter_grade)
        course_properties['percentageGrade'] = convert_float_point_to_percentage(grade.percent_grade)

        if grade.passed_timestamp and grade.letter_grade:
            course_event = COURSE_COMPLETED
            if is_program_prereq_course:
                emit_course_status_event_for_program_prereq_courses(user.id, course, COURSE_COMPLETED)
        else:
            course_event = COURSE_FAILED if has_attempted_all_modules(user, course) else None
    else:
        course_event = COURSE_STARTED
        if is_program_prereq_course:
            emit_course_status_event_for_program_prereq_courses(user.id, course, COURSE_STARTED)

    if course_event:
        segment.track(user.id, course_event, course_properties)
Exemple #24
0
    def test_track_with_standard_context(self):

        # Note that 'host' and 'path' will be urlparsed, so must be strings.
        tracking_context = {
            'accept_language': sentinel.accept_language,
            'referer': sentinel.referer,
            'username': sentinel.username,
            'session': sentinel.session,
            'ip': sentinel.ip,
            'host': 'hostname',
            'agent': sentinel.agent,
            'path': '/this/is/a/path',
            'user_id': sentinel.user_id,
            'course_id': sentinel.course_id,
            'org_id': sentinel.org_id,
            'client_id': sentinel.client_id,
        }
        with self.tracker.context('test', tracking_context):
            segment.track(sentinel.user_id, sentinel.name, self.properties)

        self.assertTrue(self.mock_segment_track.called)
        args, kwargs = self.mock_segment_track.call_args  # lint-amnesty, pylint: disable=unused-variable

        expected_segment_context = {
            'ip': sentinel.ip,
            'Google Analytics': {
                'clientId': sentinel.client_id,
            },
            'userAgent': sentinel.agent,
            'page': {
                'path': '/this/is/a/path',
                'referrer': sentinel.referer,
                'url':
                'https://hostname/this/is/a/path'  # Synthesized URL value.
            }
        }
        self.assertEqual((sentinel.user_id, sentinel.name, self.properties,
                          expected_segment_context), args)
Exemple #25
0
    def emit_event(self, user, program, suggested_course_run, completed_course_run):
        """
         Emit the Segment event which will be used by Braze to send the email
        """
        event_properties = {
            'COURSE_ONE_NAME': completed_course_run['title'],
            'PROGRAM_TYPE': program['type'],
            'PROGRAM_TITLE': program['title'],
            'COURSE_TWO_NAME': suggested_course_run['title'],
            'COURSE_TWO_SHORT_DESCRIPTION': suggested_course_run['short_description'],
            'COURSE_TWO_LINK': urljoin(settings.MKTG_URLS.get('ROOT'), suggested_course_run['marketing_url']),
            'COURSE_TWO_IMAGE_LINK': suggested_course_run['image'].get('src'),
        }
        segment.track(user.id, 'edx.bi.program.course-enrollment.nudge', event_properties)

        LOGGER.info(
            '[Program Course Nudge Email] Segment event fired to suggested. '
            'Completed Course: [%s], Program: [%s], Suggested Course: [%s], User: [%s].',
            completed_course_run['key'],
            program['uuid'],
            suggested_course_run['key'],
            user.username,
        )
    def test_track_context_with_stuff(self, tracking_context, provided_context, expected_segment_context):
        # Test first with tracking and no provided context.
        with self.tracker.context('test', tracking_context):
            segment.track(sentinel.user_id, sentinel.name, self.properties)
        args, kwargs = self.mock_segment_track.call_args  # lint-amnesty, pylint: disable=unused-variable
        assert (sentinel.user_id, sentinel.name, self.properties, expected_segment_context) == args

        # Test with provided context and no tracking context.
        segment.track(sentinel.user_id, sentinel.name, self.properties, provided_context)
        args, kwargs = self.mock_segment_track.call_args
        assert (sentinel.user_id, sentinel.name, self.properties, provided_context) == args

        # Test with provided context and also tracking context.
        with self.tracker.context('test', tracking_context):
            segment.track(sentinel.user_id, sentinel.name, self.properties, provided_context)
        assert self.mock_segment_track.called
        args, kwargs = self.mock_segment_track.call_args
        assert (sentinel.user_id, sentinel.name, self.properties, provided_context) == args
Exemple #27
0
    def test_track_context_with_stuff(self, tracking_context, provided_context,
                                      expected_segment_context):
        # Test first with tracking and no provided context.
        with self.tracker.context('test', tracking_context):
            segment.track(sentinel.user_id, sentinel.name, self.properties)
        args, kwargs = self.mock_segment_track.call_args
        self.assertEqual((sentinel.user_id, sentinel.name, self.properties,
                          expected_segment_context), args)

        # Test with provided context and no tracking context.
        segment.track(sentinel.user_id, sentinel.name, self.properties,
                      provided_context)
        args, kwargs = self.mock_segment_track.call_args
        self.assertEqual((sentinel.user_id, sentinel.name, self.properties,
                          provided_context), args)

        # Test with provided context and also tracking context.
        with self.tracker.context('test', tracking_context):
            segment.track(sentinel.user_id, sentinel.name, self.properties,
                          provided_context)
        self.assertTrue(self.mock_segment_track.called)
        args, kwargs = self.mock_segment_track.call_args
        self.assertEqual((sentinel.user_id, sentinel.name, self.properties,
                          provided_context), args)
 def test_missing_name(self):
     segment.track(sentinel.user_id, None, self.properties)
     assert not self.mock_segment_track.called
Exemple #29
0
    def get(self, request):
        """
        Return the if the course should be upsold in the mobile app, if the user has appropriate permissions.
        """
        if not MOBILE_UPSELL_FLAG.is_enabled():
            return Response({
                'show_upsell': False,
                'upsell_flag': False,
            })

        course_id = request.GET.get('course_id')
        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            return HttpResponseBadRequest("Missing or invalid course_id")

        course = CourseOverview.get_from_id(course_key)
        if not course.has_started() or course.has_ended():
            return Response({
                'show_upsell': False,
                'upsell_flag': MOBILE_UPSELL_FLAG.is_enabled(),
                'course_running': False,
            })

        user = request.user
        try:
            enrollment = CourseEnrollment.objects.select_related('course').get(
                user_id=user.id, course_id=course.id)
            user_upsell = can_show_verified_upgrade(user, enrollment)
        except CourseEnrollment.DoesNotExist:
            user_upsell = True

        basket_url = EcommerceService().upgrade_url(user, course.id)
        upgrade_price = six.text_type(
            get_cosmetic_verified_display_price(course))
        could_upsell = bool(user_upsell and basket_url)

        bucket = stable_bucketing_hash_group(MOBILE_UPSELL_EXPERIMENT, 2, user)

        if could_upsell and hasattr(
                request,
                'session') and MOBILE_UPSELL_EXPERIMENT not in request.session:
            properties = {
                'site': request.site.domain,
                'app_label': 'experiments',
                'bucket': bucket,
                'experiment': 'REV-934',
            }
            segment.track(
                user_id=user.id,
                event_name='edx.bi.experiment.user.bucketed',
                properties=properties,
            )

            # Mark that we've recorded this bucketing, so that we don't do it again this session
            request.session[MOBILE_UPSELL_EXPERIMENT] = True

        show_upsell = bool(bucket != 0 and could_upsell)
        if show_upsell:
            return Response({
                'show_upsell': show_upsell,
                'price': upgrade_price,
                'basket_url': basket_url,
            })
        else:
            return Response({
                'show_upsell': show_upsell,
                'upsell_flag': MOBILE_UPSELL_FLAG.is_enabled(),
                'experiment_bucket': bucket,
                'user_upsell': user_upsell,
                'basket_url': basket_url,
            })
Exemple #30
0
    def get_bucket(self, course_key=None, track=True):
        """
        Return which bucket number the specified user is in.

        The user may be force-bucketed if matching subordinate flags of the form
        "main_flag.BUCKET_NUM" exist. Otherwise, they will be hashed into a default
        bucket based on their username, the experiment name, and the course-run key.

        If `self.use_course_aware_bucketing` is False, the course-run key will
        be omitted from the hashing formula, thus making it so a given user
        has the same default bucket across all course runs; however, subordinate
        flags that match the course-run key will still apply.

        If `course_key` argument is omitted altogether, then subordinate flags
        will be evaluated outside of the course-run context, and the default bucket
        will be calculated as if `self.use_course_aware_bucketing` is False.

        Finally, Bucket 0 is assumed to be the control bucket and will be returned if the
        experiment is not enabled for this user and course.

        Arguments:
            course_key (Optional[CourseKey])
            track (bool):
                Whether an analytics event should be generated if the user is
                bucketed for the first time.

        Returns: int
        """
        # Keep some imports in here, because this class is commonly used at a module level, and we want to avoid
        # circular imports for any models.
        from lms.djangoapps.experiments.models import ExperimentKeyValue
        from lms.djangoapps.courseware.masquerade import get_specific_masquerading_user

        request = get_current_request()
        if not request:
            return 0

        if not hasattr(request, 'user') or not request.user.id:
            # We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users
            return 0

        user = get_specific_masquerading_user(request.user, course_key)
        if user is None:
            user = request.user
            masquerading_as_specific_student = False
        else:
            masquerading_as_specific_student = True

        # If a course key is passed in, include it in the experiment name
        # in order to separate caches and analytics calls per course-run.
        # If we are using course-aware bucketing, then also append that course key
        # to `bucketing_group_name`, such that users can be hashed into different
        # buckets for different course-runs.
        experiment_name = bucketing_group_name = self.namespaced_flag_name
        if course_key:
            experiment_name += ".{}".format(course_key)
        if course_key and self.use_course_aware_bucketing:
            bucketing_group_name += ".{}".format(course_key)

        # Check if we have a cache for this request already
        request_cache = RequestCache('experiments')
        cache_response = request_cache.get_cached_response(experiment_name)
        if cache_response.is_found:
            return cache_response.value

        # Check if the main flag is even enabled for this user and course.
        if not self.is_experiment_on(
                course_key):  # grabs user from the current request, if any
            return self._cache_bucket(experiment_name, 0)

        # Check if the enrollment should even be considered (if it started before the experiment wants, we ignore)
        if course_key and self.experiment_id is not None:
            values = ExperimentKeyValue.objects.filter(
                experiment_id=self.experiment_id).values('key', 'value')
            values = {pair['key']: pair['value'] for pair in values}

            if not self._is_enrollment_inside_date_bounds(
                    values, user, course_key):
                return self._cache_bucket(experiment_name, 0)

        # Determine the user's bucket.
        # First check if forced into a particular bucket, using our subordinate bucket flags.
        # If not, calculate their default bucket using a consistent hash function.
        for i, bucket_flag in enumerate(self.bucket_flags):
            if bucket_flag.is_enabled(course_key):
                bucket = i
                break
        else:
            bucket = stable_bucketing_hash_group(bucketing_group_name,
                                                 self.num_buckets,
                                                 user.username)

        session_key = 'tracked.{}'.format(experiment_name)
        if (track and hasattr(request, 'session')
                and session_key not in request.session
                and not masquerading_as_specific_student):
            segment.track(user_id=user.id,
                          event_name='edx.bi.experiment.user.bucketed',
                          properties={
                              'site': request.site.domain,
                              'app_label': self.waffle_namespace.name,
                              'experiment': self.flag_name,
                              'course_id':
                              str(course_key) if course_key else None,
                              'bucket': bucket,
                              'is_staff': user.is_staff,
                              'nonInteraction': 1,
                          })

            # Mark that we've recorded this bucketing, so that we don't do it again this session
            request.session[session_key] = True

        return self._cache_bucket(experiment_name, bucket)