Example #1
0
def track_forum_event(request, event_name, course, obj, data, id_map=None):
    """
    Send out an analytics event when a forum event happens. Works for threads,
    responses to threads, and comments on those responses.
    """
    user = request.user
    data['id'] = obj.id
    commentable_id = data['commentable_id']

    team = get_team(commentable_id)
    if team is not None:
        data.update(team_id=team.team_id)

    if id_map is None:
        id_map = get_cached_discussion_id_map(course, [commentable_id], user)
    if commentable_id in id_map:
        data['category_name'] = id_map[commentable_id]["title"]
        data['category_id'] = commentable_id
    data['url'] = request.META.get('HTTP_REFERER', '')
    data['user_forums_roles'] = [
        role.name for role in user.roles.filter(course_id=course.id)
    ]
    data['user_course_roles'] = [
        role.role for role in user.courseaccessrole_set.filter(course_id=course.id)
    ]

    tracker.emit(event_name, data)
Example #2
0
def subsection_grade_calculated(subsection_grade):
    """
    Emits an edx.grades.subsection.grade_calculated event
    with data from the passed subsection_grade.
    """
    event_name = SUBSECTION_GRADE_CALCULATED
    context = contexts.course_context_from_course_id(subsection_grade.course_id)
    # TODO (AN-6134): remove this context manager
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name,
            {
                'user_id': unicode(subsection_grade.user_id),
                'course_id': unicode(subsection_grade.course_id),
                'block_id': unicode(subsection_grade.usage_key),
                'course_version': unicode(subsection_grade.course_version),
                'weighted_total_earned': subsection_grade.earned_all,
                'weighted_total_possible': subsection_grade.possible_all,
                'weighted_graded_earned': subsection_grade.earned_graded,
                'weighted_graded_possible': subsection_grade.possible_graded,
                'first_attempted': unicode(subsection_grade.first_attempted),
                'subtree_edited_timestamp': unicode(subsection_grade.subtree_edited_timestamp),
                'event_transaction_id': unicode(get_event_transaction_id()),
                'event_transaction_type': unicode(get_event_transaction_type()),
                'visible_blocks_hash': unicode(subsection_grade.visible_blocks_id),
            }
        )
Example #3
0
def set_cert_generation_enabled(course_key, is_enabled):
    """Enable or disable self-generated certificates for a course.

    There are two "switches" that control whether self-generated certificates
    are enabled for a course:

    1) Whether the self-generated certificates feature is enabled.
    2) Whether self-generated certificates have been enabled for this particular course.

    The second flag should be enabled *only* when someone has successfully
    generated example certificates for the course.  This helps avoid
    configuration errors (for example, not having a template configured
    for the course installed on the workers).  The UI for the instructor
    dashboard enforces this constraint.

    Arguments:
        course_key (CourseKey): The course identifier.

    Keyword Arguments:
        is_enabled (boolean): If provided, enable/disable self-generated
            certificates for this course.

    """
    CertificateGenerationCourseSetting.set_enabled_for_course(course_key, is_enabled)
    cert_event_type = 'enabled' if is_enabled else 'disabled'
    event_name = '.'.join(['edx', 'certificate', 'generation', cert_event_type])
    tracker.emit(event_name, {
        'course_id': unicode(course_key),
    })
    if is_enabled:
        log.info(u"Enabled self-generated certificates for course '%s'.", unicode(course_key))
    else:
        log.info(u"Disabled self-generated certificates for course '%s'.", unicode(course_key))
Example #4
0
def _track_certificate_events(request, context, course, user, user_certificate):
    """
    Tracks web certificate view related events.
    """
    badge = context['badge']
    # Badge Request Event Tracking Logic
    if 'evidence_visit' in request.GET and badge:
        tracker.emit(
            'edx.badge.assertion.evidence_visited',
            {
                'user_id': user.id,
                'course_id': unicode(course.id),
                'enrollment_mode': badge.mode,
                'assertion_id': badge.id,
                'assertion_image_url': badge.data['image'],
                'assertion_json_url': badge.data['json']['id'],
                'issuer': badge.data['issuer'],
            }
        )

    # track certificate evidence_visited event for analytics when certificate_user and accessing_user are different
    if request.user and request.user.id != user.id:
        emit_certificate_event('evidence_visited', user, unicode(course.id), course, {
            'certificate_id': user_certificate.verify_uuid,
            'enrollment_mode': user_certificate.mode,
            'social_network': CertificateSocialNetworks.linkedin
        })
    def set_s3_info(self, data, _suffix=''):
        """
        Set required information of amazon web service for file uploading.

        Args:
                data: dict in JSON format
                data['aws_access_key']: Amazon Web Services access key
                data['aws_secret_key']: Amazon Web Services secret key
                data['bucketName']: Bucket name of your Amazon Web Services
                data['uploadedFileDir']: Directory for your upload files
        Returns:
                result['Success']: the boolean indicator for whether the setting is complete
        """
        if not self.get_user_is_staff():
            result = {
                'error': 'Set S3 information without permission',
                'Success': False
            }
            tracker.emit('set_s3_info', result)
            return result
        self.s3_configuration['aws_access_key'] = data['aws_access_key']
        self.s3_configuration['aws_secret_key'] = data['aws_secret_key']
        self.s3_configuration['bucketName'] = data['bucketName']
        if data['uploadedFileDir'][len(data['uploadedFileDir']) - 1] == '/':
            self.s3_configuration['uploadedFileDir'] = data['uploadedFileDir']
        else:
            self.s3_configuration['uploadedFileDir'] = (data['uploadedFileDir']
                                                        + '/')
        result = self.s3_configuration.copy()
        result['Success'] = True
        tracker.emit('set_s3_info', result)
        return result
Example #6
0
def add_cohort(course_key, name, assignment_type):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if is_cohort_exists(course_key, name):
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

    cohort = CourseCohort.create(
        cohort_name=name,
        course_id=course.id,
        assignment_type=assignment_type
    ).course_user_group

    tracker.emit(
        "edx.cohort.creation_requested",
        {"cohort_name": cohort.name, "cohort_id": cohort.id}
    )
    return cohort
Example #7
0
    def emit_event(self, event_name):
        """
        Emits an event to explicitly track course enrollment and unenrollment.
        """

        try:
            context = contexts.course_context_from_course_id(self.course_id)
            assert(isinstance(self.course_id, CourseKey))
            data = {
                'user_id': self.user.id,
                'course_id': self.course_id.to_deprecated_string(),
                'mode': self.mode,
            }

            with tracker.get_tracker().context(event_name, context):
                tracker.emit(event_name, data)

                if settings.FEATURES.get('SEGMENT_IO_LMS') and settings.SEGMENT_IO_LMS_KEY:
                    tracking_context = tracker.get_tracker().resolve_context()
                    analytics.track(self.user_id, event_name, {
                        'category': 'conversion',
                        'label': self.course_id.to_deprecated_string(),
                        'org': self.course_id.org,
                        'course': self.course_id.course,
                        'run': self.course_id.run,
                        'mode': self.mode,
                    }, context={
                        'Google Analytics': {
                            'clientId': tracking_context.get('client_id')
                        }
                    })

        except:  # pylint: disable=bare-except
            if event_name and self.course_id:
                log.exception('Unable to emit event %s for user %s and course %s', event_name, self.user.username, self.course_id)
Example #8
0
    def search(cls, query_params):

        default_params = {'page': 1,
                          'per_page': 20,
                          'course_id': query_params['course_id'],
                          'recursive': False,
                          'with_responses': True}
        params = merge_dict(default_params, strip_blank(strip_none(query_params)))

        if query_params.get('text'):
            url = cls.url(action='search')
        else:
            url = cls.url(action='get_all', params=extract(params, 'commentable_id'))
            if params.get('commentable_id'):
                del params['commentable_id']
        response = perform_request(
            'get',
            url,
            params,
            metric_tags=[u'course_id:{}'.format(query_params['course_id'])],
            metric_action='thread.search',
            paged_results=True
        )
        if query_params.get('text'):
            search_query = query_params['text']
            course_id = query_params['course_id']
            group_id = query_params['group_id'] if 'group_id' in query_params else None
            requested_page = params['page']
            total_results = response.get('total_results')
            corrected_text = response.get('corrected_text')
            # Record search result metric to allow search quality analysis.
            # course_id is already included in the context for the event tracker
            tracker.emit(
                'edx.forum.searched',
                {
                    'query': search_query,
                    'corrected_text': corrected_text,
                    'group_id': group_id,
                    'page': requested_page,
                    'total_results': total_results,
                }
            )
            log.info(
                u'forum_text_search query="{search_query}" corrected_text="{corrected_text}" course_id={course_id} group_id={group_id} page={requested_page} total_results={total_results}'.format(
                    search_query=search_query,
                    corrected_text=corrected_text,
                    course_id=course_id,
                    group_id=group_id,
                    requested_page=requested_page,
                    total_results=total_results
                )
            )

        return CommentClientPaginatedResult(
            collection=response.get('collection', []),
            page=response.get('page', 1),
            num_pages=response.get('num_pages', 1),
            thread_count=response.get('thread_count', 0),
            corrected_text=response.get('corrected_text', None)
        )
Example #9
0
def emit_setting_changed_event(user, db_table, setting_name, old_value, new_value):
    """Emits an event for a change in a setting.

    Args:
        user (User): the user that this setting is associated with.
        db_table (str): the name of the table that we're modifying.
        setting_name (str): the name of the setting being changed.
        old_value (object): the value before the change.
        new_value (object): the new value being saved.

    Returns:
        None
    """
    # Compute the maximum value length so that two copies can fit into the maximum event size
    # in addition to all the other fields recorded.
    max_value_length = settings.TRACK_MAX_EVENT / 4

    serialized_old_value, old_was_truncated = _get_truncated_setting_value(old_value, max_length=max_value_length)
    serialized_new_value, new_was_truncated = _get_truncated_setting_value(new_value, max_length=max_value_length)
    truncated_values = []
    if old_was_truncated:
        truncated_values.append("old")
    if new_was_truncated:
        truncated_values.append("new")
    tracker.emit(
        USER_SETTINGS_CHANGED_EVENT_NAME,
        {
            "setting": setting_name,
            "old": serialized_old_value,
            "new": serialized_new_value,
            "truncated": truncated_values,
            "user_id": user.id,
            "table": db_table,
        }
    )
Example #10
0
def _emit_problem_submitted_event(kwargs):
    """
    Emits a problem submitted event only if
    there is no current event transaction type,
    i.e. we have not reached this point in the
    code via a rescore or student state deletion.
    """
    root_type = get_event_transaction_type()

    if not root_type:
        root_id = get_event_transaction_id()
        if not root_id:
            root_id = create_new_event_transaction_id()
        set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE)
        tracker.emit(
            unicode(PROBLEM_SUBMITTED_EVENT_TYPE),
            {
                'user_id': unicode(kwargs['user_id']),
                'course_id': unicode(kwargs['course_id']),
                'problem_id': unicode(kwargs['usage_id']),
                'event_transaction_id': unicode(root_id),
                'event_transaction_type': unicode(PROBLEM_SUBMITTED_EVENT_TYPE),
                'weighted_earned': kwargs.get('weighted_earned'),
                'weighted_possible': kwargs.get('weighted_possible'),
            }
        )
def emit_setting_changed_event(user, db_table, setting_name, old_value, new_value):
    """Emits an event for a change in a setting.

    Args:
        user (User): the user that this setting is associated with.
        db_table (str): the name of the table that we're modifying.
        setting_name (str): the name of the setting being changed.
        old_value (object): the value before the change.
        new_value (object): the new value being saved.

    Returns:
        None
    """
    truncated_fields = truncate_fields(old_value, new_value)

    truncated_fields['setting'] = setting_name
    truncated_fields['user_id'] = user.id
    truncated_fields['table'] = db_table

    tracker.emit(
        USER_SETTINGS_CHANGED_EVENT_NAME,
        truncated_fields
    )

    # Announce field change
    USER_FIELD_CHANGED.send(sender=None, user=user, table=db_table, setting=setting_name,
                            old_value=old_value, new_value=new_value)
Example #12
0
def add_user_to_cohort(cohort, username_or_email):
    """
    Look up the given user, and if successful, add them to the specified cohort.

    Arguments:
        cohort: CourseUserGroup
        username_or_email: string.  Treated as email if has '@'

    Returns:
        Tuple of User object and string (or None) indicating previous cohort

    Raises:
        User.DoesNotExist if can't find user.
        ValueError if user already present in this cohort.
    """
    user = get_user_by_username_or_email(username_or_email)

    membership = CohortMembership(course_user_group=cohort, user=user)
    membership.save()

    tracker.emit(
        "edx.cohort.user_add_requested",
        {
            "user_id": user.id,
            "cohort_id": cohort.id,
            "cohort_name": cohort.name,
            "previous_cohort_id": membership.previous_cohort_id,
            "previous_cohort_name": membership.previous_cohort_name,
        }
    )
    return (user, membership.previous_cohort_name)
Example #13
0
 def _emit_grade_calculated_event(grade):
     """
     Emits an edx.grades.subsection.grade_calculated event
     with data from the passed grade.
     """
     # TODO: remove this context manager after completion of AN-6134
     event_name = u'edx.grades.subsection.grade_calculated'
     context = contexts.course_context_from_course_id(grade.course_id)
     with tracker.get_tracker().context(event_name, context):
         tracker.emit(
             event_name,
             {
                 'user_id': unicode(grade.user_id),
                 'course_id': unicode(grade.course_id),
                 'block_id': unicode(grade.usage_key),
                 'course_version': unicode(grade.course_version),
                 'weighted_total_earned': grade.earned_all,
                 'weighted_total_possible': grade.possible_all,
                 'weighted_graded_earned': grade.earned_graded,
                 'weighted_graded_possible': grade.possible_graded,
                 'first_attempted': unicode(grade.first_attempted),
                 'subtree_edited_timestamp': unicode(grade.subtree_edited_timestamp),
                 'event_transaction_id': unicode(get_event_transaction_id()),
                 'event_transaction_type': unicode(get_event_transaction_type()),
                 'visible_blocks_hash': unicode(grade.visible_blocks_id),
             }
         )
Example #14
0
def track_forum_event(request, event_name, course, obj, data, id_map=None):
    """
    Send out an analytics event when a forum event happens. Works for threads,
    responses to threads, and comments on those responses.
    """
    user = request.user
    data['id'] = obj.id
    commentable_id = data['commentable_id']

    if id_map is None:
        id_map = get_cached_discussion_id_map(course, [commentable_id], user)

    if commentable_id in id_map:
        data['category_name'] = id_map[commentable_id]["title"]
        data['category_id'] = commentable_id
    if len(obj.body) > TRACKING_MAX_FORUM_BODY:
        data['truncated'] = True
    else:
        data['truncated'] = False

    data['body'] = obj.body[:TRACKING_MAX_FORUM_BODY]
    data['url'] = request.META.get('HTTP_REFERER', '')
    data['user_forums_roles'] = [
        role.name for role in user.roles.filter(course_id=course.id)
    ]
    data['user_course_roles'] = [
        role.role for role in user.courseaccessrole_set.filter(course_id=course.id)
    ]

    tracker.emit(event_name, data)
Example #15
0
def upload_csv_to_report_store(rows, csv_name, course_id, timestamp):
    """
    Upload data as a CSV using ReportStore.

    Arguments:
        rows: CSV data in the following format (first column may be a
            header):
            [
                [row1_colum1, row1_colum2, ...],
                ...
            ]
        csv_name: Name of the resulting CSV
        course_id: ID of the course
    """
    report_store = ReportStore.from_config()
    report_store.store_rows(
        course_id,
        u"{course_prefix}_{csv_name}_{timestamp_str}.csv".format(
            course_prefix=course_filename_prefix_generator(course_id),
            csv_name=csv_name,
            timestamp_str=timestamp.strftime("%Y-%m-%d-%H%M")
        ),
        rows
    )
    tracker.emit(REPORT_REQUESTED_EVENT_NAME, {"report_type": csv_name, })
Example #16
0
def add_cohort(course_key, name):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if CourseUserGroup.objects.filter(course_id=course_key,
                                      group_type=CourseUserGroup.COHORT,
                                      name=name).exists():
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

    cohort = CourseUserGroup.objects.create(
        course_id=course.id,
        group_type=CourseUserGroup.COHORT,
        name=name
    )
    tracker.emit(
        "edx.cohort.creation_requested",
        {"cohort_name": cohort.name, "cohort_id": cohort.id}
    )
    return cohort
Example #17
0
    def add_resource(self, data, _suffix=''):  # pylint: disable=unused-argument
        """
        Add a new resource entry.

        Args:
                data: dict in JSON format
                data[resource_content_field]: the resource to be added. Dictionary of
                                              description, etc. as defined above
        Returns:
                result: dict in JSON format
                result['error']: error message generated if the addition fails
                result[resource_content_field]: the content of the added resource
        """
        # Construct new resource
        result = {}
        for field in self.resource_content_fields:
            result[field] = data[field]

        resource_id = stem_url(data['url'])
        self._check_redundant_resource(resource_id, 'add_resource', result)
        self._check_removed_resource(resource_id, 'add_resource', result)

        result['id'] = resource_id

        result['upvotes'] = 0
        result['downvotes'] = 0
        self.recommendations[resource_id] = dict(result)
        tracker.emit('add_resource', result)
        result["description"] = self._get_onetime_url(result["description"])
        return result
Example #18
0
    def emit_event(self, event_name):
        """
        Emits an event to explicitly track course enrollment and unenrollment.
        """

        try:
            context = contexts.course_context_from_course_id(self.course_id)
            assert isinstance(self.course_id, CourseKey)
            data = {"user_id": self.user.id, "course_id": self.course_id.to_deprecated_string(), "mode": self.mode}

            with tracker.get_tracker().context(event_name, context):
                tracker.emit(event_name, data)

                if settings.FEATURES.get("SEGMENT_IO_LMS") and settings.SEGMENT_IO_LMS_KEY:
                    tracking_context = tracker.get_tracker().resolve_context()
                    analytics.track(
                        self.user_id,
                        event_name,
                        {
                            "category": "conversion",
                            "label": self.course_id.to_deprecated_string(),
                            "org": self.course_id.org,
                            "course": self.course_id.course,
                            "run": self.course_id.run,
                            "mode": self.mode,
                        },
                        context={"Google Analytics": {"clientId": tracking_context.get("client_id")}},
                    )

        except:  # pylint: disable=bare-except
            if event_name and self.course_id:
                log.exception(
                    "Unable to emit event %s for user %s and course %s", event_name, self.user.username, self.course_id
                )
Example #19
0
def _emit_event(name, context, data):
    """
    Do the actual integration into the event-tracker
    """

    try:
        if context:
            # try to parse out the org_id from the course_id
            if 'course_id' in context:
                try:
                    course_key = CourseKey.from_string(context['course_id'])
                    context['org_id'] = course_key.org
                except InvalidKeyError:
                    # leave org_id blank
                    pass

            with tracker.get_tracker().context(name, context):
                tracker.emit(name, data)
        else:
            # if None is passed in then we don't construct the 'with' context stack
            tracker.emit(name, data)
    except KeyError:
        # This happens when a default tracker has not been registered by the host application
        # aka LMS. This is normal when running unit tests in isolation.
        log.warning(
            'Analytics tracker not properly configured. '
            'If this message appears in a production environment, please investigate'
        )
Example #20
0
def user_track(request):
    """
    Log when POST call to "event" URL is made by a user.

    GET or POST call should provide "event_type", "event", and "page" arguments.
    """
    try:
        username = request.user.username
    except:
        username = "******"

    name = _get_request_value(request, 'event_type')
    data = _get_request_value(request, 'event', {})
    page = _get_request_value(request, 'page')

    if isinstance(data, six.string_types) and len(data) > 0:
        try:
            data = json.loads(data)
            _add_user_id_for_username(data)
        except ValueError:
            pass

    context_override = contexts.course_context_from_url(page)
    context_override['username'] = username
    context_override['event_source'] = 'browser'
    context_override['page'] = page

    with eventtracker.get_tracker().context('edx.course.browser', context_override):
        eventtracker.emit(name=name, data=data)

    return HttpResponse('success')
def emit_team_event(event_name, course_key, event_data):
    """
    Emit team events with the correct course id context.
    """
    context = contexts.course_context_from_course_id(course_key)

    with tracker.get_tracker().context(event_name, context):
        tracker.emit(event_name, event_data)
Example #22
0
def _cohort_added(sender, **kwargs):
    """Emits a tracking log event each time a cohort is created"""
    instance = kwargs["instance"]
    if kwargs["created"] and instance.group_type == CourseUserGroup.COHORT:
        tracker.emit(
            "edx.cohort.created",
            {"cohort_id": instance.id, "cohort_name": instance.name}
        )
Example #23
0
    def upload_screenshot(self, request, _suffix=''):  # pylint: disable=unused-argument
        """
        Upload a screenshot for an entry of resource as a preview (typically to S3 or filesystem).

        Args:
                request: HTTP POST request
                request.POST['file'].file: the file to be uploaded
        Returns:
                response: HTTP response
                response.body (response.responseText): name of the uploaded file

        We validate that this is a valid JPG, GIF, or PNG by checking magic number, mimetype,
        and extension all correspond. We also limit to 30MB. We save the file under its MD5
        hash to (1) avoid name conflicts, (2) avoid race conditions and (3) save space.
        """
        # Check invalid file types
        image_types = {
            'jpeg': {
                'extension': [".jpeg", ".jpg"],
                'mimetypes': ['image/jpeg', 'image/pjpeg'],
                'magic': ["ffd8"]
            },
            'png': {
                'extension': [".png"],
                'mimetypes': ['image/png'],
                'magic': ["89504e470d0a1a0a"]
            },
            'gif': {
                'extension': [".gif"],
                'mimetypes': ['image/gif'],
                'magic': ["474946383961", "474946383761"]
            }
        }
        file_type_error_msg = 'Please upload an image in GIF/JPG/PNG'
        result = self._check_upload_file(
            request, image_types, file_type_error_msg, 'upload_screenshot', 31457280
        )
        if isinstance(result, Response):
            return result

        try:
            content = request.POST['file'].file.read()
            file_id = hashlib.md5(content).hexdigest()
            file_name = (file_id + '.' + result)

            fhwrite = self.fs.open(file_name, "wb")
            fhwrite.write(content)
            fhwrite.close()
        except IOError:
            return self._raise_pyfs_error('upload_screenshot')

        response = Response()
        response.body = json.dumps({'file_name': str("fs://" + file_name)})
        response.headers['Content-Type'] = 'application/json'
        tracker.emit('upload_screenshot',
                     {'uploadedFileName': response.body})
        response.status = 200
        return response
    def get_group_for_user(cls, course_key, user, user_partition, assign=True):
        """
        Returns the group from the specified user position to which the user is assigned.
        If the user has not yet been assigned, a group will be randomly chosen for them if assign flag is True.
        """
        partition_key = cls.key_for_partition(user_partition)
        group_id = course_tag_api.get_course_tag(user, course_key, partition_key)

        group = None
        if group_id is not None:
            # attempt to look up the presently assigned group
            try:
                group = user_partition.get_group(int(group_id))
            except NoSuchUserPartitionGroupError:
                # jsa: we can turn off warnings here if this is an expected case.
                log.warn(
                    u"group not found in RandomUserPartitionScheme: %r",
                    {
                        "requested_partition_id": user_partition.id,
                        "requested_group_id": group_id,
                    },
                    exc_info=True
                )

        if group is None and assign and not course_tag_api.BulkCourseTags.is_prefetched(course_key):
            if not user_partition.groups:
                raise UserPartitionError('Cannot assign user to an empty user partition')

            # pylint: disable=fixme
            # TODO: had a discussion in arch council about making randomization more
            # deterministic (e.g. some hash).  Could do that, but need to be careful not
            # to introduce correlation between users or bias in generation.
            group = cls.RANDOM.choice(user_partition.groups)

            # persist the value as a course tag
            course_tag_api.set_course_tag(user, course_key, partition_key, group.id)

            # emit event for analytics
            # FYI - context is always user ID that is logged in, NOT the user id that is
            # being operated on. If instructor can move user explicitly, then we should
            # put in event_info the user id that is being operated on.
            event_name = 'xmodule.partitions.assigned_user_to_partition'
            event_info = {
                'group_id': group.id,
                'group_name': group.name,
                'partition_id': user_partition.id,
                'partition_name': user_partition.name
            }
            # pylint: disable=fixme
            # TODO: Use the XBlock publish api instead
            with tracker.get_tracker().context(event_name, {}):
                tracker.emit(
                    event_name,
                    event_info,
                )

        return group
Example #25
0
    def handle_vote(self, data, _suffix=''):  # pylint: disable=unused-argument
        """
        Add/Subtract a vote to a resource entry.

        Args:
                data: dict in JSON format
                data['id']: the ID of the resouce which was upvoted/downvoted
                data['event']: recommender_upvote or recommender_downvote
        Returns:
                result: dict in JSON format
                result['error']: error message generated if the process fails
                result['oldVotes']: original # of votes
                result['newVotes']: votes after this action
                result['toggle']: boolean indicator for whether the resource
                                  was switched from downvoted to upvoted
        """
        resource_id = self._validate_resource(data['id'], data['event'])

        result = {}
        result['id'] = resource_id
        is_event_upvote = (data['event'] == 'recommender_upvote')
        result['oldVotes'] = (self.recommendations[resource_id]['upvotes'] -
                              self.recommendations[resource_id]['downvotes'])

        upvoting_existing_upvote = is_event_upvote and resource_id in self.upvoted_ids
        downvoting_existing_downvote = not is_event_upvote and resource_id in self.downvoted_ids

        if upvoting_existing_upvote:
            # While the user is trying to upvote a resource which has been
            # upvoted, we restore the resource to unvoted
            self.upvoted_ids.remove(resource_id)
            self.recommendations[resource_id]['upvotes'] -= 1
        elif downvoting_existing_downvote:
            # While the user is trying to downvote a resource which has
            # been downvoted, we restore the resource to unvoted
            self.downvoted_ids.remove(resource_id)
            self.recommendations[resource_id]['downvotes'] -= 1
        elif is_event_upvote:  # New upvote
            if resource_id in self.downvoted_ids:
                self.downvoted_ids.remove(resource_id)
                self.recommendations[resource_id]['downvotes'] -= 1
                result['toggle'] = True
            self.upvoted_ids.append(resource_id)
            self.recommendations[resource_id]['upvotes'] += 1
        else:  # New downvote
            if resource_id in self.upvoted_ids:
                self.upvoted_ids.remove(resource_id)
                self.recommendations[resource_id]['upvotes'] -= 1
                result['toggle'] = True
            self.downvoted_ids.append(resource_id)
            self.recommendations[resource_id]['downvotes'] += 1

        result['newVotes'] = (self.recommendations[resource_id]['upvotes'] -
                              self.recommendations[resource_id]['downvotes'])
        tracker.emit(data['event'], result)
        return result
Example #26
0
def emit_course_goal_event(sender, instance, **kwargs):
    name = 'edx.course.goal.added' if kwargs.get('created', False) else 'edx.course.goal.updated'
    tracker.emit(
        name,
        {
            'goal_key': instance.goal_key,
        }
    )
    if settings.LMS_SEGMENT_KEY:
        update_google_analytics(name, instance.user.id)
    def is_user_staff(self, _data, _suffix=''):
        """
        Return whether the user is staff.

        Returns:
                is_user_staff: indicator for whether the user is staff
        """
        result = {'is_user_staff': self.get_user_is_staff()}
        tracker.emit('is_user_staff', result)
        return result
Example #28
0
def emit_course_goal_event(sender, instance, **kwargs):
    """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)
Example #29
0
    def flag_resource(self, data, _suffix=''):  # pylint: disable=unused-argument
        """
        Flag (or unflag) an entry of problematic resource and give the reason. This shows in a
        list for staff to review.

        Args:
                data: dict in JSON format
                data['id']: the ID of the problematic resouce
                data['isProblematic']: the boolean indicator for whether the resource is being
                                       flagged or unflagged. Only flagging works.
                data['reason']: the reason why the user believes the resource is problematic
        Returns:
                result: dict in JSON format
                result['reason']: the new reason
                result['oldReason']: the old reason
                result['id']: the ID of the problematic resouce
                result['isProblematic']: the boolean indicator for whether the resource
                                         is now flagged
        """
        result = {}
        result['id'] = data['id']
        result['isProblematic'] = data['isProblematic']
        result['reason'] = data['reason']

        user_id = self.get_user_id()

        # If already flagged, update the reason for the flag
        if data['isProblematic']:
            # If already flagged, update the reason
            if data['id'] in self.flagged_ids:
                result['oldReason'] = self.flagged_reasons[
                    self.flagged_ids.index(data['id'])]
                self.flagged_reasons[
                    self.flagged_ids.index(data['id'])] = data['reason']
            # Otherwise, flag it.
            else:
                self.flagged_ids.append(data['id'])
                self.flagged_reasons.append(data['reason'])

                if user_id not in self.flagged_accum_resources:
                    self.flagged_accum_resources[user_id] = {}
            self.flagged_accum_resources[user_id][data['id']] = data['reason']
        # Unflag resource. Currently unsupported.
        else:
            if data['id'] in self.flagged_ids:
                result['oldReason'] = self.flagged_reasons[
                    self.flagged_ids.index(data['id'])]
                result['reason'] = ''
                idx = self.flagged_ids.index(data['id'])
                del self.flagged_ids[idx]
                del self.flagged_reasons[idx]

                del self.flagged_accum_resources[user_id][data['id']]
        tracker.emit('flag_resource', result)
        return result
Example #30
0
    def search(cls, query_params):

        default_params = {"page": 1, "per_page": 20, "course_id": query_params["course_id"], "recursive": False}
        params = merge_dict(default_params, strip_blank(strip_none(query_params)))

        if query_params.get("text"):
            url = cls.url(action="search")
        else:
            url = cls.url(action="get_all", params=extract(params, "commentable_id"))
            if params.get("commentable_id"):
                del params["commentable_id"]
        response = perform_request(
            "get",
            url,
            params,
            metric_tags=[u"course_id:{}".format(query_params["course_id"])],
            metric_action="thread.search",
            paged_results=True,
        )
        if query_params.get("text"):
            search_query = query_params["text"]
            course_id = query_params["course_id"]
            group_id = query_params["group_id"] if "group_id" in query_params else None
            requested_page = params["page"]
            total_results = response.get("total_results")
            corrected_text = response.get("corrected_text")
            # Record search result metric to allow search quality analysis.
            # course_id is already included in the context for the event tracker
            tracker.emit(
                "edx.forum.searched",
                {
                    "query": search_query,
                    "corrected_text": corrected_text,
                    "group_id": group_id,
                    "page": requested_page,
                    "total_results": total_results,
                },
            )
            log.info(
                u'forum_text_search query="{search_query}" corrected_text="{corrected_text}" course_id={course_id} group_id={group_id} page={requested_page} total_results={total_results}'.format(
                    search_query=search_query,
                    corrected_text=corrected_text,
                    course_id=course_id,
                    group_id=group_id,
                    requested_page=requested_page,
                    total_results=total_results,
                )
            )
        return (
            response.get("collection", []),
            response.get("page", 1),
            response.get("num_pages", 1),
            response.get("corrected_text"),
        )
 def publish_event(event_name, result, **kwargs):
     """
     Helper function to publish an event for analytics purposes
     """
     event_data = {
         "location": unicode(location),
         "previous_count": previous_count,
         "result": result,
         "max_count": max_count,
     }
     event_data.update(kwargs)
     context = contexts.course_context_from_course_id(
         location.course_key)
     if user_id:
         context['user_id'] = user_id
     full_event_name = "edx.librarycontentblock.content.{}".format(
         event_name)
     with tracker.get_tracker().context(full_event_name, context):
         tracker.emit(full_event_name, event_data)
Example #32
0
    def _track_index_request(event_name, indexed_count, location=None):
        """Track content index requests.

        Arguments:
            location (str): The ID of content to be indexed.
            event_name (str):  Name of the event to be logged.
        Returns:
            None

        """
        data = {
            "indexed_count": indexed_count,
            'category': 'courseware_index',
        }

        if location:
            data['location_id'] = location

        tracker.emit(event_name, data)
Example #33
0
    def set_client_configuration(self, data, _suffix=''):  # pylint: disable=unused-argument
        """
        Set the parameters for student-view, client side configurations.

        Args:
                data: dict in JSON format. Keys in data:
                  disable_dev_ux: feature flag for any new UX under development
                                  which should not appear in prod
                  entries_per_page: the number of resources in each page
                  page_span: page range in pagination control
                  intro_enable: Should we show the users a short usage tutorial
                                the first time they see the XBlock?
        """
        self.intro_enabled = data['intro_enable']
        for key in ['disable_dev_ux', 'page_span', 'entries_per_page']:
            self.client_configuration[key] = data[key]

        tracker.emit('set_client_configuration', data)
        return {}
Example #34
0
def _cohort_membership_changed(sender, **kwargs):
    """Emits a tracking log event each time cohort membership is modified"""
    def get_event_iter(user_id_iter, cohort_iter):
        """
        Returns a dictionary containing a mashup of cohort and user information for the given lists
        """
        return ({
            "cohort_id": cohort.id,
            "cohort_name": cohort.name,
            "user_id": user_id
        } for user_id in user_id_iter for cohort in cohort_iter)

    action = kwargs["action"]
    instance = kwargs["instance"]
    pk_set = kwargs["pk_set"]
    reverse = kwargs["reverse"]

    if action == "post_add":
        event_name = "edx.cohort.user_added"
    elif action in ["post_remove", "pre_clear"]:
        event_name = "edx.cohort.user_removed"
    else:
        return

    if reverse:
        user_id_iter = [instance.id]
        if action == "pre_clear":
            cohort_iter = instance.course_groups.filter(
                group_type=CourseUserGroup.COHORT)
        else:
            cohort_iter = CourseUserGroup.objects.filter(
                pk__in=pk_set, group_type=CourseUserGroup.COHORT)
    else:
        cohort_iter = [
            instance
        ] if instance.group_type == CourseUserGroup.COHORT else []
        if action == "pre_clear":
            user_id_iter = (user.id for user in instance.users.all())
        else:
            user_id_iter = pk_set

    for event in get_event_iter(user_id_iter, cohort_iter):
        tracker.emit(event_name, event)
Example #35
0
 def _send_assertion_created_event(self, user, assertion):
     """
     Send an analytics event to record the creation of a badge assertion.
     """
     tracker.emit(
         'edx.badge.assertion.created', {
             'user_id': user.id,
             'badge_slug': assertion.badge_class.slug,
             'badge_badgr_server_slug':
             assertion.badge_class.badgr_server_slug,
             'badge_name': assertion.badge_class.display_name,
             'issuing_component': assertion.badge_class.issuing_component,
             'course_id': str(assertion.badge_class.course_id),
             'enrollment_mode': assertion.badge_class.mode,
             'assertion_id': assertion.id,
             'assertion_image_url': assertion.image_url,
             'assertion_json_url': assertion.assertion_url,
             'issuer': assertion.data.get('issuer'),
         })
Example #36
0
def _emit_event(kwargs):
    """
    Emits a problem submitted event only if there is no current event
    transaction type, i.e. we have not reached this point in the code via a
    rescore or student state deletion.

    If the event transaction type has already been set and the transacation is
    a rescore, emits a problem rescored event.
    """
    root_type = get_event_transaction_type()

    if not root_type:
        root_id = get_event_transaction_id()
        if not root_id:
            root_id = create_new_event_transaction_id()
        set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE)
        tracker.emit(
            unicode(PROBLEM_SUBMITTED_EVENT_TYPE), {
                'user_id': unicode(kwargs['user_id']),
                'course_id': unicode(kwargs['course_id']),
                'problem_id': unicode(kwargs['usage_id']),
                'event_transaction_id': unicode(root_id),
                'event_transaction_type':
                unicode(PROBLEM_SUBMITTED_EVENT_TYPE),
                'weighted_earned': kwargs.get('weighted_earned'),
                'weighted_possible': kwargs.get('weighted_possible'),
            })

    if root_type in [GRADES_RESCORE_EVENT_TYPE, GRADES_OVERRIDE_EVENT_TYPE]:
        current_user = get_current_user()
        instructor_id = getattr(current_user, 'id', None)
        tracker.emit(
            unicode(GRADES_RESCORE_EVENT_TYPE), {
                'course_id': unicode(kwargs['course_id']),
                'user_id': unicode(kwargs['user_id']),
                'problem_id': unicode(kwargs['usage_id']),
                'new_weighted_earned': kwargs.get('weighted_earned'),
                'new_weighted_possible': kwargs.get('weighted_possible'),
                'only_if_higher': kwargs.get('only_if_higher'),
                'instructor_id': unicode(instructor_id),
                'event_transaction_id': unicode(get_event_transaction_id()),
                'event_transaction_type': unicode(root_type),
            })
Example #37
0
def password_reset(request):
    """
    Attempts to send a password reset e-mail.
    """

    password_reset_email_limiter = PasswordResetEmailRateLimiter()

    if password_reset_email_limiter.is_rate_limit_exceeded(request):
        AUDIT_LOG.warning("Password reset rate limit exceeded")
        return HttpResponse(_(
            "Your previous request is in progress, please try again in a few moments."
        ),
                            status=403)

    form = PasswordResetFormNoActive(request.POST)
    if form.is_valid():
        form.save(use_https=request.is_secure(),
                  from_email=configuration_helpers.get_value(
                      'email_from_address', settings.DEFAULT_FROM_EMAIL),
                  request=request)
        # When password change is complete, a "edx.user.settings.changed" event will be emitted.
        # But because changing the password is multi-step, we also emit an event here so that we can
        # track where the request was initiated.
        tracker.emit(
            SETTING_CHANGE_INITIATED, {
                "setting": "password",
                "old": None,
                "new": None,
                "user_id": request.user.id,
            })
        destroy_oauth_tokens(request.user)
    else:
        # bad user? tick the rate limiter counter
        AUDIT_LOG.info("Bad password_reset user passed in.")
        password_reset_email_limiter.tick_request_counter(request)

    return JsonResponse({
        'success':
        True,
        'value':
        render_to_string('registration/password_reset_done.html', {}),
    })
Example #38
0
    def _track_index_request(cls, event_name, category, indexed_count):
        """Track content index requests.

        Arguments:
            event_name (str):  Name of the event to be logged.
            category (str): category of indexed items
            indexed_count (int): number of indexed items
        Returns:
            None

        """
        data = {
            "indexed_count": indexed_count,
            'category': category,
        }

        tracker.emit(
            event_name,
            data
        )
Example #39
0
    def emit_event(self, event_name):
        """
        Emits an event to explicitly track course enrollment and unenrollment.
        """

        try:
            context = contexts.course_context_from_course_id(self.course_id)
            data = {
                'user_id': self.user.id,
                'course_id': self.course_id,
                'mode': self.mode,
            }

            with tracker.get_tracker().context(event_name, context):
                tracker.emit(event_name, data)
        except:  # pylint: disable=bare-except
            if event_name and self.course_id:
                log.exception(
                    'Unable to emit event %s for user %s and course %s',
                    event_name, self.user.username, self.course_id)
Example #40
0
def course_grade_passed_first_time(user_id, course_id):
    """
    Emits an event edx.course.grade.passed.first_time
    with data from the passed course_grade.
    """
    event_name = COURSE_GRADE_PASSED_FIRST_TIME_EVENT_TYPE
    context = contexts.course_context_from_course_id(course_id)
    context_enterprise = get_enterprise_event_context(user_id, course_id)
    context.update(context_enterprise)
    # TODO (AN-6134): remove this context manager
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name,
            {
                'user_id': str(user_id),
                'course_id': str(course_id),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(get_event_transaction_type())
            }
        )
Example #41
0
    def emit_event(self, event_name):
        """
        Emits an event to explicitly track course enrollment and unenrollment.
        """

        try:
            context = contexts.course_context_from_course_id(self.course_id)
            assert (isinstance(self.course_id, CourseKey))
            data = {
                'user_id': self.user.id,
                'course_id': self.course_id.to_deprecated_string(),
                'mode': self.mode,
            }

            with tracker.get_tracker().context(event_name, context):
                tracker.emit(event_name, data)

                if settings.FEATURES.get(
                        'SEGMENT_IO_LMS') and settings.SEGMENT_IO_LMS_KEY:
                    tracking_context = tracker.get_tracker().resolve_context()
                    analytics.track(
                        self.user_id,
                        event_name, {
                            'category': 'conversion',
                            'label': self.course_id.to_deprecated_string(),
                            'org': self.course_id.org,
                            'course': self.course_id.course,
                            'run': self.course_id.run,
                            'mode': self.mode,
                        },
                        context={
                            'Google Analytics': {
                                'clientId': tracking_context.get('client_id')
                            }
                        })

        except:  # pylint: disable=bare-except
            if event_name and self.course_id:
                log.exception(
                    'Unable to emit event %s for user %s and course %s',
                    event_name, self.user.username, self.course_id)
    def accum_flagged_resource(self, _data, _suffix=''):  # pylint: disable=unused-argument
        """
        Accumulate the flagged resource ids and reasons from all students
        """
        if not self.get_user_is_staff():
            msg = self.ugettext('Tried to access flagged resources without staff permission')
            self._error_handler(msg, 'accum_flagged_resource')
        result = {
            'flagged_resources': {}
        }
        for _, flagged_accum_resource_map in self.flagged_accum_resources.iteritems():
            for resource_id in flagged_accum_resource_map:
                if resource_id in self.removed_recommendations:
                    continue
                if resource_id not in result['flagged_resources']:
                    result['flagged_resources'][resource_id] = []
                if flagged_accum_resource_map[resource_id] != '':
                    result['flagged_resources'][resource_id].append(flagged_accum_resource_map[resource_id])

        tracker.emit('accum_flagged_resource', result)
        return result
Example #43
0
def emit_setting_changed_event(user, db_table, setting_name, old_value,
                               new_value):
    """Emits an event for a change in a setting.

    Args:
        user (User): the user that this setting is associated with.
        db_table (str): the name of the table that we're modifying.
        setting_name (str): the name of the setting being changed.
        old_value (object): the value before the change.
        new_value (object): the new value being saved.

    Returns:
        None
    """
    truncated_fields = truncate_fields(old_value, new_value)

    truncated_fields['setting'] = setting_name
    truncated_fields['user_id'] = user.id
    truncated_fields['table'] = db_table

    tracker.emit(USER_SETTINGS_CHANGED_EVENT_NAME, truncated_fields)
Example #44
0
def emit_certificate_event(event_name, user, course_id, course=None, event_data=None):
    """
    Emits certificate event.
    """
    event_name = '.'.join(['edx', 'certificate', event_name])
    if course is None:
        course = modulestore().get_course(course_id, depth=0)
    context = {
        'org_id': course.org,
        'course_id': unicode(course_id)
    }
    data = {
        'user_id': user.id,
        'course_id': unicode(course_id),
        'certificate_url': get_certificate_url(user.id, course_id)
    }
    event_data = event_data or {}
    event_data.update(data)

    with tracker.get_tracker().context(event_name, context):
        tracker.emit(event_name, event_data)
Example #45
0
def track_share_redirect(request__unused, course_id, network,
                         student_username):
    """
    Tracks when a user downloads a badge for sharing.
    """
    course_id = CourseLocator.from_string(course_id)
    assertion = get_object_or_404(BadgeAssertion,
                                  user__username=student_username,
                                  course_id=course_id)
    tracker.emit(
        'edx.badge.assertion.shared', {
            'course_id': unicode(course_id),
            'social_network': network,
            'assertion_id': assertion.id,
            'assertion_json_url': assertion.data['json']['id'],
            'assertion_image_url': assertion.image_url,
            'user_id': assertion.user.id,
            'enrollment_mode': assertion.mode,
            'issuer': assertion.data['issuer'],
        })
    return redirect(assertion.image_url)
Example #46
0
def grade_updated(**kwargs):
    """
    Emits the appropriate grade-related event after checking for which
    event-transaction is active.

    Emits a problem.submitted event only if there is no current event
    transaction type, i.e. we have not reached this point in the code via
    an outer event type (such as problem.rescored or score_overridden).
    """
    root_type = get_event_transaction_type()

    if not root_type:
        root_id = get_event_transaction_id()
        if not root_id:
            root_id = create_new_event_transaction_id()
        set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE)
        tracker.emit(
            str(PROBLEM_SUBMITTED_EVENT_TYPE),
            {
                'user_id': str(kwargs['user_id']),
                'course_id': str(kwargs['course_id']),
                'problem_id': str(kwargs['usage_id']),
                'event_transaction_id': str(root_id),
                'event_transaction_type': str(PROBLEM_SUBMITTED_EVENT_TYPE),
                'weighted_earned': kwargs.get('weighted_earned'),
                'weighted_possible': kwargs.get('weighted_possible'),
            }
        )

    elif root_type in [GRADES_RESCORE_EVENT_TYPE, GRADES_OVERRIDE_EVENT_TYPE]:
        current_user = get_current_user()
        instructor_id = getattr(current_user, 'id', None)
        tracker.emit(
            str(root_type),
            {
                'course_id': str(kwargs['course_id']),
                'user_id': str(kwargs['user_id']),
                'problem_id': str(kwargs['usage_id']),
                'new_weighted_earned': kwargs.get('weighted_earned'),
                'new_weighted_possible': kwargs.get('weighted_possible'),
                'only_if_higher': kwargs.get('only_if_higher'),
                'instructor_id': str(instructor_id),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(root_type),
            }
        )

    elif root_type in [SUBSECTION_OVERRIDE_EVENT_TYPE]:
        tracker.emit(
            str(root_type),
            {
                'course_id': str(kwargs['course_id']),
                'user_id': str(kwargs['user_id']),
                'problem_id': str(kwargs['usage_id']),
                'only_if_higher': kwargs.get('only_if_higher'),
                'override_deleted': kwargs.get('score_deleted', False),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(root_type),
            }
        )
    def endorse_resource(self, data, _suffix=''):  # pylint: disable=unused-argument
        """
        Endorse an entry of resource. This shows the students the
        resource has the staff seal of approval.

        Args:
                data: dict in JSON format
                data['id']: the ID of the resouce to be endorsed
        Returns:
                result: dict in JSON format
                result['error']: the error message generated when the endorsement fails
                result['id']: the ID of the resouce to be endorsed
                result['status']: endorse the resource or undo it
        """
        # Auth+auth
        if not self.get_user_is_staff():
            msg = self.ugettext('Endorse resource without permission')
            self._error_handler(msg, 'endorse_resource')

        resource_id = self._validate_resource(data['id'], 'endorse_resource')

        result = {}
        result['id'] = resource_id

        # Unendorse previously endorsed resource
        if resource_id in self.endorsed_recommendation_ids:
            result['status'] = 'undo endorsement'
            endorsed_index = self.endorsed_recommendation_ids.index(
                resource_id)
            del self.endorsed_recommendation_ids[endorsed_index]
            del self.endorsed_recommendation_reasons[endorsed_index]
        # Endorse new resource
        else:
            result['reason'] = data['reason']
            result['status'] = 'endorsement'
            self.endorsed_recommendation_ids.append(resource_id)
            self.endorsed_recommendation_reasons.append(data['reason'])

        tracker.emit('endorse_resource', result)
        return result
Example #48
0
def get_stats(request, course_id, problem_id):
    """ et stats for a single problem (LoncapaProblem).

    The problem id is passed has a GET paramater with 'problem_id' key.

    Args:
         course_id (str): The course id as string.
     """
    start_time = time.time()
    store = modulestore()
    course_key = CourseKey.from_string(course_id)

    try:
        problem = utils.fetch_problem(store, course_key, problem_id)
    except ItemNotFoundError:
        raise Http404()
    problem_monitor = ProblemMonitor(problem)
    problem_monitor.get_student_answers()
    tracker.emit("course_dashboard.problem_stats.views.get_stats",
                 {'task-time': time.time() - start_time})

    return HttpResponse(problem_monitor.get_html())
    def remove_resource(self, data, _suffix=''):
        """
        Remove an entry of resource. This removes it from the student
        view, and prevents students from being able to add it back.

        Args:
                data: dict in JSON format
                data['id']: the ID of the resouce to be removed
                data['reason']: the reason why the resouce was removed
        Returns:
                result: dict in JSON format
                result['error']: the error message generated when the removal fails
                result['recommendation']: (Dict) the removed resource
                result['recommendation']['reason']: the reason why the resouce was removed

        """
        # Auth+auth
        if not self.get_user_is_staff():
            msg = self.ugettext(
                "You don't have the permission to remove this resource")
            self._error_handler(msg, 'remove_resource')

        resource_id = self._validate_resource(data['id'], 'remove_resource')

        # Grab a copy of the resource for the removed list
        # (swli: I reorganized the code a bit. First copy, then delete. This is more fault-tolerant)
        result = {}
        result['id'] = resource_id
        removed_resource = deepcopy(self.recommendations[resource_id])
        removed_resource['reason'] = data['reason']

        # Add it to removed resources and remove it from main resource list.
        self.removed_recommendations[resource_id] = removed_resource
        del self.recommendations[resource_id]

        # And return
        result['recommendation'] = removed_resource
        tracker.emit('remove_resource', result)
        return result
    def get_client_configuration(self):
        """
        Return the parameters for client-side configuration settings.

        Returns:
                disable_dev_ux: feature flag for any new UX under development
                                which should not appear in prod
                entries_per_page: the number of resources in each page
                page_span: page range in pagination control
                intro: whether to take users through a short usage tutorial
                       the first time they see the RecommenderXBlock
                is_user_staff: whether the user is staff
        """
        result = self.client_configuration.copy()
        result['is_user_staff'] = self.get_user_is_staff()
        result['intro'] = not self.seen and self.intro_enabled
        if not self.seen:
            # Mark the user who interacted with the XBlock first time as seen,
            # in order not to show the usage tutorial in future.
            self.seen = True
        tracker.emit('get_client_configuration', result)
        return result
Example #51
0
def subsection_grade_calculated(subsection_grade):
    """
    Emits an edx.grades.subsection.grade_calculated event
    with data from the passed subsection_grade.
    """
    event_name = SUBSECTION_GRADE_CALCULATED
    context = contexts.course_context_from_course_id(
        subsection_grade.course_id)
    # TODO (AN-6134): remove this context manager
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name, {
                'user_id':
                unicode(subsection_grade.user_id),
                'course_id':
                unicode(subsection_grade.course_id),
                'block_id':
                unicode(subsection_grade.usage_key),
                'course_version':
                unicode(subsection_grade.course_version),
                'weighted_total_earned':
                subsection_grade.earned_all,
                'weighted_total_possible':
                subsection_grade.possible_all,
                'weighted_graded_earned':
                subsection_grade.earned_graded,
                'weighted_graded_possible':
                subsection_grade.possible_graded,
                'first_attempted':
                unicode(subsection_grade.first_attempted),
                'subtree_edited_timestamp':
                unicode(subsection_grade.subtree_edited_timestamp),
                'event_transaction_id':
                unicode(get_event_transaction_id()),
                'event_transaction_type':
                unicode(get_event_transaction_type()),
                'visible_blocks_hash':
                unicode(subsection_grade.visible_blocks_id),
            })
Example #52
0
 def _emit_grade_calculated_event(grade):
     """
     Emits an edx.grades.subsection.grade_calculated event
     with data from the passed grade.
     """
     # TODO: remove this context manager after completion of AN-6134
     event_name = u'edx.grades.subsection.grade_calculated'
     context = contexts.course_context_from_course_id(grade.course_id)
     with tracker.get_tracker().context(event_name, context):
         tracker.emit(
             event_name, {
                 'user_id':
                 unicode(grade.user_id),
                 'course_id':
                 unicode(grade.course_id),
                 'block_id':
                 unicode(grade.usage_key),
                 'course_version':
                 unicode(grade.course_version),
                 'weighted_total_earned':
                 grade.earned_all,
                 'weighted_total_possible':
                 grade.possible_all,
                 'weighted_graded_earned':
                 grade.earned_graded,
                 'weighted_graded_possible':
                 grade.possible_graded,
                 'first_attempted':
                 unicode(grade.first_attempted),
                 'subtree_edited_timestamp':
                 unicode(grade.subtree_edited_timestamp),
                 'event_transaction_id':
                 unicode(get_event_transaction_id()),
                 'event_transaction_type':
                 unicode(get_event_transaction_type()),
                 'visible_blocks_hash':
                 unicode(grade.visible_blocks_id),
             })
def upload_csv_to_report_store(rows, csv_name, course_id, timestamp):
    """
    Upload data as a CSV using ReportStore.

    Arguments:
        rows: CSV data in the following format (first column may be a
            header):
            [
                [row1_colum1, row1_colum2, ...],
                ...
            ]
        csv_name: Name of the resulting CSV
        course_id: ID of the course
    """
    report_store = ReportStore.from_config()
    report_store.store_rows(
        course_id, u"{course_prefix}_{csv_name}_{timestamp_str}.csv".format(
            course_prefix=course_filename_prefix_generator(course_id),
            csv_name=csv_name,
            timestamp_str=timestamp.strftime("%Y-%m-%d-%H%M")), rows)
    tracker.emit(REPORT_REQUESTED_EVENT_NAME, {
        "report_type": csv_name,
    })
Example #54
0
def course_grade_calculated(course_grade):
    """
    Emits an edx.grades.course.grade_calculated event
    with data from the passed course_grade.
    """
    event_name = COURSE_GRADE_CALCULATED
    context = contexts.course_context_from_course_id(course_grade.course_id)
    # TODO (AN-6134): remove this context manager
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name,
            {
                'user_id': str(course_grade.user_id),
                'course_id': str(course_grade.course_id),
                'course_version': str(course_grade.course_version),
                'percent_grade': course_grade.percent_grade,
                'letter_grade': str(course_grade.letter_grade),
                'course_edited_timestamp': str(course_grade.course_edited_timestamp),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(get_event_transaction_type()),
                'grading_policy_hash': str(course_grade.grading_policy_hash),
            }
        )
Example #55
0
 def _emit_grade_calculated_event(grade):
     """
     Emits an edx.grades.subsection.grade_calculated event
     with data from the passed grade.
     """
     tracker.emit(
         u'edx.grades.subsection.grade_calculated',
         {
             'user_id': unicode(grade.user_id),
             'course_id': unicode(grade.course_id),
             'block_id': unicode(grade.usage_key),
             'course_version': unicode(grade.course_version),
             'weighted_total_earned': grade.earned_all,
             'weighted_total_possible': grade.possible_all,
             'weighted_graded_earned': grade.earned_graded,
             'weighted_graded_possible': grade.possible_graded,
             'first_attempted': unicode(grade.first_attempted),
             'subtree_edited_timestamp': unicode(grade.subtree_edited_timestamp),
             'event_transaction_id': unicode(get_event_transaction_id()),
             'event_transaction_type': unicode(get_event_transaction_type()),
             'visible_blocks_hash': unicode(grade.visible_blocks_id),
         }
     )
Example #56
0
def add_cohort(course_key, name, assignment_type):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if is_cohort_exists(course_key, name):
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

    cohort = CourseCohort.create(
        cohort_name=name, course_id=course.id,
        assignment_type=assignment_type).course_user_group

    tracker.emit("edx.cohort.creation_requested", {
        "cohort_name": cohort.name,
        "cohort_id": cohort.id
    })
    return cohort
Example #57
0
 def _emit_grade_calculated_event(grade):
     """
     Emits an edx.grades.course.grade_calculated event
     with data from the passed grade.
     """
     # TODO: remove this context manager after completion of AN-6134
     event_name = u'edx.grades.course.grade_calculated'
     context = contexts.course_context_from_course_id(grade.course_id)
     with tracker.get_tracker().context(event_name, context):
         tracker.emit(
             event_name,
             {
                 'user_id': unicode(grade.user_id),
                 'course_id': unicode(grade.course_id),
                 'course_version': unicode(grade.course_version),
                 'percent_grade': grade.percent_grade,
                 'letter_grade': unicode(grade.letter_grade),
                 'course_edited_timestamp': unicode(grade.course_edited_timestamp),
                 'event_transaction_id': unicode(get_event_transaction_id()),
                 'event_transaction_type': unicode(get_event_transaction_type()),
                 'grading_policy_hash': unicode(grade.grading_policy_hash),
             }
         )
Example #58
0
def set_cert_generation_enabled(course_key, is_enabled):
    """Enable or disable self-generated certificates for a course.

    There are two "switches" that control whether self-generated certificates
    are enabled for a course:

    1) Whether the self-generated certificates feature is enabled.
    2) Whether self-generated certificates have been enabled for this particular course.

    The second flag should be enabled *only* when someone has successfully
    generated example certificates for the course.  This helps avoid
    configuration errors (for example, not having a template configured
    for the course installed on the workers).  The UI for the instructor
    dashboard enforces this constraint.

    Arguments:
        course_key (CourseKey): The course identifier.

    Keyword Arguments:
        is_enabled (boolean): If provided, enable/disable self-generated
            certificates for this course.

    """
    CertificateGenerationCourseSetting.set_enabled_for_course(
        course_key, is_enabled)
    cert_event_type = 'enabled' if is_enabled else 'disabled'
    event_name = '.'.join(
        ['edx', 'certificate', 'generation', cert_event_type])
    tracker.emit(event_name, {
        'course_id': unicode(course_key),
    })
    if is_enabled:
        log.info(u"Enabled self-generated certificates for course '%s'.",
                 unicode(course_key))
    else:
        log.info(u"Disabled self-generated certificates for course '%s'.",
                 unicode(course_key))
Example #59
0
def task_track(request_info, task_info, event_type, event, page=None):
    """
    Logs tracking information for events occuring within celery tasks.

    The `event_type` is a string naming the particular event being logged,
    while `event` is a dict containing whatever additional contextual information
    is desired.

    The `request_info` is a dict containing information about the original
    task request.  Relevant keys are `username`, `ip`, `agent`, and `host`.
    While the dict is required, the values in it are not, so that {} can be
    passed in.

    In addition, a `task_info` dict provides more information about the current
    task, to be stored with the `event` dict.  This may also be an empty dict.

    The `page` parameter is optional, and allows the name of the page to
    be provided.
    """

    # supplement event information with additional information
    # about the task in which it is running.
    data = dict(event, **task_info)

    context_override = contexts.course_context_from_url(page)
    context_override.update({
        'username': request_info.get('username', 'unknown'),
        'ip': request_info.get('ip', 'unknown'),
        'agent': request_info.get('agent', 'unknown'),
        'host': request_info.get('host', 'unknown'),
        'event_source': 'task',
        'page': page,
    })

    with eventtracker.get_tracker().context('edx.course.task',
                                            context_override):
        eventtracker.emit(name=event_type, data=data)
Example #60
0
def add_cohort(course_key, name):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if CourseUserGroup.objects.filter(course_id=course_key,
                                      group_type=CourseUserGroup.COHORT,
                                      name=name).exists():
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

    cohort = CourseUserGroup.objects.create(course_id=course.id,
                                            group_type=CourseUserGroup.COHORT,
                                            name=name)
    tracker.emit("edx.cohort.creation_requested", {
        "cohort_name": cohort.name,
        "cohort_id": cohort.id
    })
    return cohort