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

Add no_redirect query param for course, script, and puzzle show routes #28311

Merged
merged 25 commits into from
May 16, 2019

Conversation

maddiedierker
Copy link
Contributor

@maddiedierker maddiedierker commented May 1, 2019

LP-346

Adds the ability to override our course/script version redirect logic by adding the no_redirect query parameter to the request. This "redirect override" will then be stored in the Rails session to be remembered for the life of the session.

This is most useful for internal users (especially content translators) who need to visit an old or unstable course/script without being redirected. This is primarily useful for logged out users who want to visit these courses/scripts one or more times (since logged out users and students are the only users who are ever redirected away from courses/scripts).

To use this feature, visit a course, script, or level URL with the ?no_redirect=true query param:

studio.code.org/courses/csd-2017?no_redirect=true                 # course
studio.code.org/s/coursea-2017?no_redirect=true                   # script
studio.code.org/s/coursea-2017/stage/1/puzzle/1?no_redirect=true  # level

Redirect overrides are stored in the session as follows:

session[:course_version_overrides] = ['csp-2017', 'csd-2017']
session[:script_version_overrides] = ['coursea-2017', 'csp1-2017']

If a script has an override, its course (if present) is implicitly overridden as well, and vice versa.

Note: the value of this query param is ignored -- we just check for the presence of the no_redirect key in our params hash.

@@ -382,10 +382,10 @@ def script_level_params(script_level)
end

test "show: redirect to latest stable script version in family for logged out user if one exists" do
courseg_2018 = create :script, name: 'courseg-2018', family_name: 'courseg', version_year: '2018'
Script.stubs(:latest_stable_version).returns(courseg_2018)
courseg_2017 = create :script, name: 'courseg-2017', family_name: 'courseg', version_year: '2017', is_stable: true
Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: this is just clean-up, not changing test behavior

test "show: redirect to latest assigned script version in family for student if one exists" do
sign_in @student

courseg_2018 = create :script, name: 'courseg-2018', family_name: 'courseg', version_year: '2018'
Script.stubs(:latest_assigned_version).returns(courseg_2018)
courseg_2017 = create :script, name: 'courseg-2017', family_name: 'courseg', version_year: '2017', is_stable: true
Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: this is just clean-up, not changing test behavior

@islemaster
Copy link
Contributor

I know Dave suggested an alternative at one point:

...remove the version redirect check on e.g. /s/coursea-2019/next. Once you let the user go to that url once, they'll have progress in that script, and will be allowed to go back to any part of it in the future.

On the surface it sounds simpler. As part of this PR, will you document why we chose this solution instead?

@@ -62,7 +62,8 @@ def show
end

# Attempt to redirect user if we think they ended up on the wrong course overview page.
if redirect_course = redirect_course(course)
# Do not redirect users that have added the 'no_redirect' query parameter to the request.
if !params[:no_redirect] && redirect_course = redirect_course(course)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm tempted to push the no_redirect check down into the redirect_course helper (or redirect_script in other cases). I think it's because the rest of the "do we redirect" logic is hidden down in that layer. I don't feel too bad about referencing params from the helper, since in every case it's still implemented as part of the same controller.

That said, it wouldn't actually remove any duplication and might be considered surprising behavior. Your call.


get :show, params: {course_name: 'csp-2017', no_redirect: "true"}

assert_response :ok
Copy link
Contributor

Choose a reason for hiding this comment

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

My own ignorance: I had to think carefully and look some things up to understand that this checks for exactly 200 OK, unlike assert_response :success which matches any 2XX, and that either would confirm that the response to this request was not a (3XX) redirect. 🤦‍♂️

@maddiedierker
Copy link
Contributor Author

@islemaster re: your comment --

I know Dave suggested an alternative at one point:

...remove the version redirect check on e.g. /s/coursea-2019/next. Once you let the user go to that url once, they'll have progress in that script, and will be allowed to go back to any part of it in the future.

On the surface it sounds simpler. As part of this PR, will you document why we chose this solution instead?

part of the request from the international team (Amanda and I talked with Suky about it) was to have a single URL that would let the user bypass our redirect logic, so to achieve this, it seemed simplest to just ignore the redirect logic if the URL has ?no_redirect=true. that way, we can share links like https://studio.code.org/courses/csp-2017?no_redirect=true with translators

@davidsbailey
Copy link
Member

I think the idea with using /next is that we wanted to let the user navigate around the course without getting sent back to the latest stable version. For example, if you send a signed-out user to https://studio.code.org/courses/csp-2019?no_redirect=true and then they click on the first unit, they will get sent back to csp-2018 since that is the latest stable version. is that outcome acceptable?

@maddiedierker
Copy link
Contributor Author

good question, dave. i think that outcome is acceptable because it gives us the flexibility to send a user to a specific level, where they would "make progress" so that they are no longer redirected to the latest stable version (this, to me, is the same as using /next to get around version redirects), or any user can visit a course/script/level once with the no_redirect param if it's a one-time-only need.

@davidsbailey
Copy link
Member

Hi Maddie, the behavior I'm seeing is that once you go to http://localhost-studio.code.org:3000/s/csd1-2019?no_redirect=true, there's no way to ' "make progress" so that they are no longer redirected to the latest stable version '. These are the steps I'm taking:

  1. go to http://localhost-studio.code.org:3000/s/csd1-2019?no_redirect=true --> no redirect
  2. click on the first puzzle or the continue button --> redirected to /s/csd1-2018

Is there a different way you've found to make progress from http://localhost-studio.code.org:3000/s/csd1-2019?no_redirect=true ? it's ok if this needs to be implemented across multiple PRs, but want to know that this builds in the right direction.

@maddiedierker
Copy link
Contributor Author

ah, i'm sorry, i should have been more specific in my original comment. these are the steps i was thinking:

a user who wants to disable redirects permanently for a course or script would do the following:

  1. go to an individual puzzle -- http://localhost-studio.code.org:3000/s/coursea-2017/stage/1/puzzle/1?no_redirect=true --> no redirect
  2. go to any other course/script page -- http://localhost-studio.code.org:3000/s/coursea-2017 --> no redirect

visiting an individual will provide a permanent override for version redirects, or a user can visit something like http://localhost-studio.code.org:3000/s/coursea-2019?no_redirect=true for a one-time override (curriculum folks and other internal users have asked for this as well, especially for courses/scripts that are still in development like the 2019 versions)

@davidsbailey
Copy link
Member

go to an individual puzzle -- http://localhost-studio.code.org:3000/s/coursea-2017/stage/1/puzzle/1?no_redirect=true --> no redirect
go to any other course/script page -- http://localhost-studio.code.org:3000/s/coursea-2017 --> no redirect

after pulling down your branch, when i follow these steps while signed out, I am not seeing the same result as you. I am getting redirected to coursea-2018. I think perhaps an additional step is needed, of finishing the level, so that progress is created. can you try the steps you listed in a fresh incognito window to determine if we're seeing the same thing?

@maddiedierker
Copy link
Contributor Author

maddiedierker commented May 2, 2019

🤦‍♀ you're right, this solution only works for signed in users. unfortunately, we will have the same issue with the /next solution because the redirect code that checks if a user has progress relies on a user being signed in:

has_progress = user.scripts.include?(self) || course&.has_progress?(user)
return true if has_progress

i'll talk to Amanda to figure out if this is a case we want to solve for (a signed out user who wants to permanently override versioning redirects for a course/script). if we do solve for this case, i see this path forward:

  1. use a before_action to check for the no_redirect param. if it's present, set a cookie for the relevant course/script version
  2. in the controller actions, check for the cookie (rather than the query param) to override redirect logic

the above sounds a little heavy-handed, but i can't think of a better solution... thoughts?

@davidsbailey
Copy link
Member

ahh, that explains it, I had not tried for signed-in users. Signed-out users do have progress, but unfortunately it is stored in localStorage in their browser, which we can't access on the server.

Based on that, I think you are on the right track with using a cookie to persist this setting. However, it may be easier to use the rails session instead, since they are like cookies but hide more details away. Here is an example of rails session being used:

If you do go with a cookie instead, here is one thought:

  • normally we would set one cookie name per environment so that they did not get confused with each other. otherwise, it could cause some confusion for developers when they access a ?no_redirect url in one environment and it affects behavior in another

whichever approach you decide:

  • may want to ignore the cookie/session setting if the user is signed-in, since in that case we can determine reliably (using the logic already implemented in this PR) where the user belongs.

@maddiedierker
Copy link
Contributor Author

ooo, i like the idea of using the rails session, so i'll try that first. thanks for your help on this!

@maddiedierker
Copy link
Contributor Author

@davidsbailey @islemaster I refactored the solution to store course/script version redirect overrides in the Rails session -- PTAL!

Copy link
Contributor

@islemaster islemaster left a comment

Choose a reason for hiding this comment

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

Very cool - the Rails session seems like an elegant solution to this problem.

Will you add to the PR description an example case of where/why we'd want to use this queryparam?

@@ -0,0 +1,37 @@
module VersionRedirectOverrider
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to have a file- or module-level comment here giving a prose description of the desired redirect logic, and/or a link out to a spec describing the desired behavior in detail. Even with the good tests you've written, I worry that the desired behavior is complex enough, and the related logic spread out enough, that it'll be challenging to reconstruct a mental model for these redirects in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, good call! i'll add a description and some usage info to the module

Copy link
Member

@davidsbailey davidsbailey left a comment

Choose a reason for hiding this comment

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

nice work Maddie! A few comments inline.

script_overrides = script_version_overrides(session)
course_overrides = course_version_overrides(session)

script_overrides.include?(script.name) || course_overrides.include?(script.course&.name)
Copy link
Member

Choose a reason for hiding this comment

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

nice call making this a module so it can be used many places.

there's a fair bit of complexity we're adding here, between storing lists of scripts + courses, and checking both before deciding whether to redirect a script or not. the simple alternative would be to just store one "no redirect" setting per user. I think the added complexity is worth it though, since we really want to the limit the chances that students will end up with progress in the wrong version of a course.

lastly, IIRC the docs say that things stored in the rails session expire when the browser window is closed, but that could be worth confirming -- it might be bad if one person accessing a no_redirect url on a shared computer made it behave that way forever for signed-out users.

id: courseg_2017_stage_1_script_level.position,
no_redirect: "true"
}
assert_response :ok
Copy link
Member

Choose a reason for hiding this comment

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

optional: it's a bit hard to see that this is an apples-to-apples comparison against the following test which does the same but without no_redirect. my recommendation would be to either extract common setup steps, or to just drop this get / assert at the end of the following test so that you can see the no_redirect param is making the difference.

this PR has dragged on a bit already, so feel free to skip and keep in mind for next time.

end

test "show: do not redirect to latest stable version if no_redirect query param is supplied" do
get :show, params: {id: @coursez_2017.name, no_redirect: "true"}
Copy link
Member

Choose a reason for hiding this comment

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

similar to above, it would make this test case more clear to add a get / assert above this line showing the behavior without no_redirect

@maddiedierker maddiedierker merged commit 83879f5 into staging May 16, 2019
@maddiedierker maddiedierker deleted the version-redirect-workaround branch May 16, 2019 20:08
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.

None yet

3 participants