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
Save solution images into artist and playlab level files #16840
Conversation
@aoby , can you please review the rails code? @islemaster , can you please review the javascript? @joshlory , please take a look at the overall approach to make sure that this meets your needs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSLGTM.
return { | ||
...reportData, | ||
image, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for not mutating arguments!
For clarification, the level file edited in this PR can be seen here in the product: https://studio.code.org/s/coursec/stage/6/puzzle/8 , and the solution image linked from the PR description represents the solution to that level. |
} | ||
assert_response :success | ||
level = assigns(:level) | ||
assert_equal level.properties[:toolbox_blocks.to_s], @program |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be assert_equal expected, actual
, it appears as if you have the arguments flipped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also why :toolbox_blocks.to_s
? If you need the string, just specify it directly: 'toolbox_blocks'
. Same below
game_id: @level.game.id, | ||
type: 'toolbox_blocks', | ||
program: @program, | ||
image: 'stub-image-data', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These parameters appear to be (mostly) duplicated across tests. It might make sense to define a default_params
in the setup block, then modify it appropriately for each test. In addition to being DRY, it makes it more clear what part of the param is under test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've extracted default params for the 5 tests pertaining to update_blocks.
@@ -770,4 +770,19 @@ def can_view_teacher_markdown? | |||
def include_multi_answers?(standalone) | |||
standalone || current_user.try(:should_see_inline_answer?, @script_level) | |||
end | |||
|
|||
def get_level_source_image(level_image, level_source_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document the function. Is level_image
a string? That level_source_id
is an integer or nil. It returns a LevelSourceImage
or nil. Etc.
@@ -770,4 +770,19 @@ def can_view_teacher_markdown? | |||
def include_multi_answers?(standalone) | |||
standalone || current_user.try(:should_see_inline_answer?, @script_level) | |||
end | |||
|
|||
def get_level_source_image(level_image, level_source_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a function header comment here. This function is rather complex, and has some side effects I wouldn't necessarily expect from a get_
method.
I'm not clear to me what it's doing. It seems to take an image, save it to S3, and construct a LevelSourceImage
model around that? Or if a LevelSourceImage
already exists, then it finds and returns that and ignores the supplied image?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to find_or_create_level_source_image
|
||
CDO.stubs(:disable_s3_image_uploads).returns(false) | ||
LevelSourceImage.any_instance.expects(:save_to_s3).never | ||
create(:level_source, :with_image, level: @level, data: @program) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the comment relate to? Are we ensuring that we get a real S3 url? I don't see anything to that effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The preceding comment relates to these 3 lines. I was trying to only tell you why, but maybe I took it too far. I've extracted a method for this and documented the steps more clearly.
} | ||
assert_response :success | ||
level = assigns(:level) | ||
assert_equal level.properties[:toolbox_blocks.to_s], @program |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also why :toolbox_blocks.to_s
? If you need the string, just specify it directly: 'toolbox_blocks'
. Same below
@aoby @ashercodeorg PTAL. I've added comments and addressed other feedback. |
@@ -3,6 +3,8 @@ | |||
class LevelsControllerTest < ActionController::TestCase | |||
include Devise::Test::ControllerHelpers | |||
|
|||
default_update_blocks_params = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't be accessible anywhere. Member variables in ruby must be preceded with a @
, and code here in the class will be executed when the class is loaded, not instantiated.
Since setup
runs before each test case you don't need to initialize the member, just define it directly. If you want to use it without the @
you can add an accessor with attr_reader :default_update_blocks_params
, or instead of in setup define it as a private method so it's lazily constructed as in this example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've followed your suggestion, but FYI that tests were passing for some reason the way that I had it. Not sure I can explain why exactly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird. I played around with it and confirmed that it worked. But not for the reason it seems. This has to do with the closures generated for the blocks sent to the test
command when the class is defined.
See this example:
class C
temp_var = 'value'
define_method :with_closure do
puts "with closure: #{defined?(temp_var)}"
end
def without_closure
puts "without closure: #{defined?(temp_var)}"
end
define_method :nested do
print "nested, "
without_closure
end
end
c = C.new
c.with_closure
c.without_closure
c.nested
The output is:
with closure: local-variable
without closure:
nested, without closure:
It shows up in the closure as if it were a local variable, but doesn't show up anywhere else.
That is only AFAICT because the blocks were defined in the same context. That variable is only in scope while the class is loaded, not accessible anywhere else later.
Similarly,
class C
@outside = nil
def initialize
@inside = nil
end
def m
puts "outside: #{defined?(@outside)}, inside: #{defined?(@inside)}"
end
end
C.new.m
outputs
outside: , inside: instance-variable
The instance variable only persists when it's defined in a method. The one defined in the class body is lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool. TIL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, once the default test params are fixed and all tests pass. Thanks for the explanatory refactor!
Quick update: this is now working in playlab also, since we are already sending the feedback image to the server when the solution blocks are updated in playlab here. I've added a solution image url for a single playlab level to this PR, and updated the PR description to point to it. |
Background
For stage extras (see spec), we want to be able to show images of the bonus levels that the students can choose from. These images should show the solution of the level in question. These levels could be artist or playlab levels. A different solution is already being used to obtain these images for maze levels.
Description
This PR proposes saving the image at the time that the levelbuilder edits the level solution. This guarantees that the solution image stays up to date when the solution is edited, and puts the solution image url in a predictable place in the database where it can be retrieved when needed. It also has the downside that each of the ~100 levels that will be included in stage extras will need to have their solutions manually run in levelbuilder once in order to generate the solution image and url.
Verification
I've run one artist solution and one playlab solution to generate the thumbnail image urls for those levels. You can see them here:
artist script level: https://studio.code.org/s/coursec/stage/6/puzzle/8
playlab script level: https://studio.code.org/s/coursec/stage/13/puzzle/1
Future work
This PR only generates solution images for artist and playlab levels. A slightly different approach may be needed to obtain the actual image for other level types, but the solution image url will still be stored in the same way in the level file for those other level types.