Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Anton/store video progress #2365

Merged
merged 1 commit into from
Feb 11, 2014
Merged

Conversation

valera-rozuvan
Copy link
Contributor

persist student progress in video

The following test cases are achieved with this PR:

Student logins to edx, plays video, selects position,
closes browser, opens video, position is restored

Given a video with a start time of :30 and an end time of 1:00
And a student who navigated away from the video was at :45
    (in between start time and end time)
When the student returns to the same video
Then the video should resume playing from :45.

Given a video with a start time of :30 and an end time of 1:00
And a student who navigated away from the video when the slider was at :15
When the student returns to the same video
Then the video should resume playing from :30

Given a video with a start time of :30 and an end time of 1:00
And a student who navigated away from the video after moving the video to 1:15,
When the student returns to the same video
Then the video should resume playing from :30

JIRA issue

BLD-385

Reviewers

@@ -193,7 +193,7 @@ def to_json(self, value):
if not value:
return "00:00:00"

if isinstance(value, float): # backward compatibility
if isinstance(value, float) or isinstance(value, int): # backward compatibility
Copy link
Contributor

Choose a reason for hiding this comment

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

isinstance(value, (float, int))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@davestgermain You are right! Python docs on isinstance reveals that the second argument can indeed be a tuple of type objects. Thx!

@@ -193,7 +193,7 @@ def to_json(self, value):
if not value:
return "00:00:00"

if isinstance(value, float): # backward compatibility
if isinstance(value, (float, int)): # backward compatibility
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add test.

@valera-rozuvan
Copy link
Contributor Author

@polesye You wrote that I should add tests for https://github.com/edx/edx-platform/pull/2365#discussion-diff-9415398R196 . After working with @auraz , we came up with a solution where the change:

if isinstance(value, (float, int)):  # backward compatibility

is no longer necessary. Please see edx/edx-platform@a2e1e22 .

seconds = seconds % 60
minutes = minutes % 60

"#{pad(hours)}:#{pad(minutes)}:#{pad(seconds % 60)}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this function used only when we send data to server and its result is not user facing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye Yes. This function was added for the purpose of sending the correctly formatted time string to the server. I decided to add it here in the hope that in the future it might be useful in other places. Code reuse!

Copy link
Contributor

Choose a reason for hiding this comment

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

I ask it, because if result of this function is planned to be visible for users, a11y should be added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye It will not be user-facing. So no need for internationalization.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please leave comment about that.

@polesye
Copy link
Contributor

polesye commented Feb 5, 2014

@valera-rozuvan Did you write js unit tests?

@@ -8,6 +8,8 @@

afterEach(function () {
$('source').remove();
window['VideoState'] = {};
window['VideoState']['id'] = {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Stylistic nitpick: In Javascript a.b and a['b'] are identical. I prefer the former when you're dealing with static strings. Can you change these lines to:

window.VideoState = {};
window.VideoState.id = {};

@singingwolfboy
Copy link
Contributor

👍

@valera-rozuvan
Copy link
Contributor Author

@polesye No. I did not write JavaScript unit tests.

@polesye
Copy link
Contributor

polesye commented Feb 5, 2014

Please add tests for saveState and updatePlayTime functions.

@polesye
Copy link
Contributor

polesye commented Feb 5, 2014

And I'm waiting for acceptance tests.

data.position = Math.round(data.position);
this.storage.setItem('position', data.position, true);

data.position = Time.formatFull(data.position);
Copy link
Contributor

Choose a reason for hiding this comment

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

We need round value just when send position to the server.

this.storage.setItem('position', data.position, true);
data.position =  Time.formatFull(data.position);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye If the server stores the time as a HH:MM:SS value, and writes position into the data-position attribute of the HTML template as an integer, why do we need to preserve the decimal point on the user's PC? I think it is better to have it rounded before we store using the storage class.

Copy link
Contributor

Choose a reason for hiding this comment

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

@valera-rozuvan No matter how do we store this value on user's PC (It's just a property of global object, not a cookie), so I don't see a reason to make this redundant rounding. Time.formatFull uses Math.floor under the hood.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye Done.

@valera-rozuvan
Copy link
Contributor Author

@polesye I have implemented acceptance test to check that position in video is stored on page refresh. All tests pass. All comments by you and @singingwolfboy have been addressed. Please list what other tests I have to implement so that @Lyla-Fischer understands how much work on this PR is left to be done.

# length of the video. Right now "0:10 / 1:55", and "0:11 / 1:55" are
# hard coded, so the test will break if the default video will be
# changed.
world.wait_for(lambda _: world.css_html('.vidtime')[:4] == '0:10' or world.css_html('.vidtime')[:4] == '0:11')
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe, that even if you break js method seekTo, this condition will be passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye I have tried this. I am aware that you can use {} and .format() methods. However, the test fails when you do this. Please check for yourself.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you had an issues with curly braces.

Format strings contain “replacement fields” surrounded by curly braces {}. Anything that is not contained in braces is considered literal text, which is copied unchanged to the output. If you need to include a brace character in the literal text, it can be escaped by doubling: {{ and }}.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye You were right = ) Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye The condition will not be passed. This is because world.wait_for() has a default wait time of 5 seconds. If we are waiting for 10 seconds of video to play, the test will fail before it gets to 10 seconds, because it will timeout after 5 seconds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@polesye So the only way this test will pass if the video starts playing from the 10th second.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, I suggest to set timeout explicitly, because default value can be changed. And I think world.wait_for(lambda _: world.css_html('.vidtime')[:4] == '0:11') will be enough.

@valera-rozuvan
Copy link
Contributor Author

@polesye I have added another acceptance test. How many more should I add?

@polesye
Copy link
Contributor

polesye commented Feb 7, 2014

@valera-rozuvan 2 tests that cover following behaviors:

Given a video with a start time of :30 and an end time of 1:00
And a student who navigated away from the video when the slider was at :15
When the student returns to the same video
Then the video should resume playing from :30

Given a video with a start time of :30 and an end time of 1:00
And a student who navigated away from the video after moving the video to 1:15,
When the student returns to the same video
Then the video should resume playing from :30

@valera-rozuvan
Copy link
Contributor Author

@polesye I have added the 2 tests you requested, and also addressed your comment

Ok, I suggest to set timeout explicitly, because default value
can be changed. And I think
world.wait_for(lambda _: world.css_html('.vidtime')[:4] == '0:11')
will be enough.

What else?

@valera-rozuvan
Copy link
Contributor Author

@polesye I have simplified Then I see video starts playing from "0:10" position case. What else?

@polesye
Copy link
Contributor

polesye commented Feb 7, 2014

I should check it manually.

@@ -215,7 +215,7 @@
}

$.each(attrs, function (name, value) {
return result = result && element.attr(name) === value;
result = result && element.attr(name) === value;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like rather this code could be improved by using _.every:

toHaveAttrs: function(attrs) {
  if ($.isEmptyObject(attrs)) {
    return false;
  }
  var element = this.actual;
  return _.every(attrs, function(value, name) {
    return element.attr(name) === value;
  });

@polesye
Copy link
Contributor

polesye commented Feb 10, 2014

I found that it doesn't store position sometimes (especially when you're reloading the page without pausing the video).

I think that will be less confusing for users to show them an actual position (in vcr and progress slider position), before the video starts playing. But it shouldn't be a blocker for this PR and could be done in separate PR.


return result;
return _.every(attrs, function (value, name) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@valera-rozuvan
Copy link
Contributor Author

@polesye Created the JIRA issue "Show start time or starting position on slider and VCR" BLD-823.

if key == 'position' or key == u'position':
relative_position = RelativeTime.isotime_to_timedelta(data[key])
# setattr(self, key, relative_position)
self.position = relative_position
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@singingwolfboy There is a bug in this line. It does not save to actual database. When the browser closes, and you open the browser again, the value of position is once again the default 0. What should I do to update the position value in the database?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nedbat Ned, also please comment on this. @auraz suggested that I ask you.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know why it wouldn't save to the database. Are you sure there's no exception being thrown? @cpennington any clues?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nedbat What query should I run in mongo shell in order to absolutely be sure that the position is 0. I want to run it when the browser is open, see that it is in fact 0, then trigger the saving of new position via AJAX request to handle_ajax(), and then check once again that the database has position set to 0.

@valera-rozuvan
Copy link
Contributor Author

@auraz and @polesye I have visually confirmed using the Robomongo tool that position is not being stored in the database. Please look in the following JSON representing the 2 entries in the DB that are modified when I work with the Video:

/* 5 */
{
    "_id" : {
        "tag" : "i4x",
        "org" : "private",
        "course" : "01",
        "category" : "video",
        "name" : "01aadcb12187481788350e558e1ac903",
        "revision" : null
    },
    "definition" : {
        "data" : {
            "data" : ""
        },
        "children" : []
    },
    "metadata" : {
        "display_name" : "Video",
        "sub" : "",
        "start_time" : "00:00:00",
        "html5_sources" : [],
        "youtube_id_1_0" : "OEoXaMPEzfM",
        "end_time" : "00:00:12"
    }
}

/* 6 */
{
    "_id" : {
        "tag" : "i4x",
        "org" : "private",
        "course" : "01",
        "category" : "video",
        "name" : "01aadcb12187481788350e558e1ac903",
        "revision" : "draft"
    },
    "definition" : {
        "data" : {
            "data" : ""
        },
        "children" : []
    },
    "metadata" : {
        "display_name" : "Video",
        "sub" : "",
        "start_time" : "00:00:00",
        "html5_sources" : [],
        "youtube_id_1_0" : "OEoXaMPEzfM",
        "end_time" : "00:00:12"
    }
}

You can see that start_time and end_time are present. When I change the values of start time and end time in the advanced tab of the Editor, the values are updated and I can see the new values in Robomongo. However, I never see the property position in Robomongo.

@cpennington , @singingwolfboy , @nedbat Please help!

@valera-rozuvan
Copy link
Contributor Author

@nedbat , @cpennington , @singingwolfboy Maybe it has something to do with how position is defined?

position = RelativeTime(
    help="Current position in the video",
    scope=Scope.user_state,
    default=datetime.timedelta(seconds=0)
)

@cpennington
Copy link
Contributor

The position field in video_module.py has scope user_state, which means that it's stored in the relational database, in the courseware_studentmodule table in the lms, and stored in the users session in Studio.

@singingwolfboy singingwolfboy removed their assignment Feb 10, 2014
@valera-rozuvan
Copy link
Contributor Author

@cpennington , @singingwolfboy , @nedbat , @auraz , @polesye I have fixed the bug with position. It turned out that this property was already used in field state in table courseware_studentmodule. The solution was to rename this property. As can be seen, this helped. All tests are passing.

@valera-rozuvan
Copy link
Contributor Author

@polesye Please finish with your review.

@polesye
Copy link
Contributor

polesye commented Feb 11, 2014

👍

Student logins to edx, plays video, selects position, closes
browser, opens video, position is restored.

BLD-385
valera-rozuvan added a commit that referenced this pull request Feb 11, 2014
@valera-rozuvan valera-rozuvan merged commit d8144b0 into master Feb 11, 2014
@valera-rozuvan valera-rozuvan deleted the anton/store-video-progress branch February 11, 2014 14:16
jenkins-ks pushed a commit to nttks/edx-platform that referenced this pull request Dec 22, 2017
* Correctly format date block dates.

(cherry picked from commit 22cb307)

* ECOM-3673 Update the course end date description text to remove the reference to certificate when course enrollment is in audit mode

(cherry picked from commit 649e8b1)

* Fixes for courseware date formatting/translation.

(cherry picked from commit 80bfa36)

 Conflicts:
	lms/djangoapps/courseware/date_summary.py

* Fix date summaries with Unicode format issues.

This issue would show up when date formatting strings are translated
with Unicode characters (like when testing with fake Esperanto
translations).

(cherry picked from commit c1a59cc)

* Converts the dates on the dashboard, sidebar navigation, and important course dates to user specified time zone.

(cherry picked from commit 0bf8fc4)

 Conflicts:
	common/djangoapps/util/date_utils.py
	common/lib/xmodule/xmodule/course_module.py
	common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py
	lms/djangoapps/ccx/tests/test_models.py
	lms/djangoapps/courseware/views/index.py
	lms/djangoapps/student_account/views.py
	lms/templates/dashboard/_dashboard_course_listing.html
	openedx/core/djangoapps/content/course_overviews/models.py
	openedx/core/djangoapps/user_api/preferences/api.py
	openedx/core/djangoapps/user_api/preferences/tests/test_views.py

* Display JST for survey's answer datetime. openedx#2146

* Display JST for section's deadline and course start/end datetime. openedx#2146

* Display JST for face-to-face start/end datetime on Django Admin. openedx#2146

* Display JST for ORA2 settings. openedx#2146

* Add the validation of the schedule dates before 1900 openedx#2284 (openedx#2298)

* Implements command count_initial_registration_course openedx#2221 (openedx#2296)

* Add GaCourseEditorRole and GaLmsCourseStaffRole openedx#2150 (openedx#2306)

* Add GaCourseEditorRole and GaLmsCourseStaffRole openedx#2150

* Add UT for studio openedx#2150

* Fix GaCourseEditor has access to after the course closed openedx#2150

* GaLmsCourseStaff can use the same function as the course staff at the staff debug info openedx#2150

* Fix UT and Bok choy openedx#2150

* Remove extra line feed openedx#2150

* Fix comment openedx#2150

* Mod conditions openedx#2150

* Move judgment conditions to template openedx#2150

* Fix comment openedx#2150

* Remove unused code openedx#2150

* Mod conditions for GaCourseEditor but a global staff openedx#2150

* Revert parameter CourseDetails.update_from_json openedx#2150

* Mod check for staff acccess at LMS for GaCourseEditor openedx#2150

* Rename GaCourseEditorRole -> GaGlobalCourseCreatorRole openedx#2150

* Rename GA_COURSE_EDITOR_USER_INFO -> GA_GLOBAL_COURSE_CREATOR_USER_INFO openedx#2150

* Rename GA_ACCESS_CHECK_TYPE_COURSE_EDITOR -> GA_ACCESS_CHECK_TYPE_GLOBAL_COURSE_CREATOR openedx#2150

* Rename Ga_Course_Editor -> Ga_Global_Course_Creator openedx#2150

* Rename gacourseeditor -> gaglobalcoursecreator openedx#2150

* Rename GaCourseEditor -> GaGlobalCourseCreator openedx#2150

* Rename course_editor -> global_course_creator openedx#2150

* Mod hide staff debug info for GaGlobalCourseCreatorRole openedx#2150

* Fix UT for GaGlobalCourseCreator openedx#2150

* Rename UT openedx#2150

* Fix skipped UT openedx#2150

* Remove unused code openedx#2150

* Fix argument openedx#2150

* Remove unused code openedx#2150

* Fix ambiguous conditions openedx#2150

* Rename GaLmsCourseStaffRole -> GaCourseScorerRole openedx#2150

* Rename GA_LMS_COURSE_STAFF_USER_INFO -> GA_COURSE_SCORER_USER_INFO openedx#2150

* Ga_Lms_Course_Staff -> Ga_Course_Scorer openedx#2150

* Rename galmscoursestaff -> gacoursescorer openedx#2150

* Rename GaLmsCourseStaff -> GaCourseScorer openedx#2150

* Rename lms_course_staff -> course_scorer openedx#2150

* Fix translation openedx#2150

* Fix comment openedx#2150

*  Add personal info mask at resign openedx#2148 (openedx#2316)

* Add personal info mask at unenroll openedx#2148

* Fix Review.

* Add Unit Test.

* Add JST date fields near UTC date fields. openedx#2230 (openedx#2307)

* Add JST date fields near UTC date fields. openedx#2230

* Fix Review

* Fix Review 2.

* Add yml Files

* Add option of progress restriction that makes users need to pass prob… (openedx#2305)

* Add option of progress restriction that makes users need to pass problems. openedx#2147

* Skip Bok-Choy CoursewareProgressRestrictionTest temporarily
because this may cause an error of other test

* Fix review 1 openedx#2147

* Fix review 2 openedx#2147

* Fix to use different username and email from test_ga_register.py
so that no error occurs on test_register_and_activate. openedx#2147

* Fix review 3 openedx#2147

* Fix review 4 openedx#2147

* Fix review 5-1

* Fix review 5-2

* Fix review 5-3

* Fix review 5-4

* Fix review 5-5

* Fix review 5-6

* Fix review 5-7

* Fix review 5-8

* Fix review 5-9

* Fix review 5-10

* Fix review 5-12

* Fix review 5-14
revert 5-13

* Fix review 5-15

* Fix review 5-16

* Fix review 6-1 openedx#2147

* Fix review 6-2 openedx#2147

* Fix review 6-3 openedx#2147

* Fix UTC message in survey page. openedx#2340 (openedx#2345)

* Fix bug for manage_user_standing page. openedx#2347 (openedx#2348)

* Fix bug and add failed testcase for mask_resigned_user command. openedx#2362 (openedx#2365)

* Add confirm for account_disabled of manage_user_standing openedx#2366 (openedx#2367)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants