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
New methods to access and update skip_content_moderation value #23432
Conversation
@@ -14,7 +14,7 @@ def initialize(storage_id) | |||
@table = PEGASUS_DB[:storage_apps] | |||
end | |||
|
|||
def create(value, ip:, type: nil, published_at: nil, remix_parent_id: nil) | |||
def create(value, ip:, type: nil, published_at: nil, remix_parent_id: nil, skip_content_moderation: false) |
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.
Do we ever need to create a storage_apps row with skip_content_moderation: true
? If so, let's add tests for this case. If not, maybe we don't want it to be a parameter here.
@@ -138,6 +139,15 @@ def get_abuse(channel_id) | |||
row[:abuse_score] | |||
end | |||
|
|||
def get_skip_content_moderation_value(channel_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.
In #23406 you settled on disable-auto-moderation
as the consistent language for this feature. Our server code should use the same language, unless there's a particular reason it's different.
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.
Idiomatic ruby nit: Name methods that return a boolean value as a query def auto_moderation_disabled?(channel_id)
raise NotFound, "channel `#{channel_id}` not found" unless row | ||
|
||
update_content_moderation = @table.where(id: id).exclude(state: 'deleted').update({skip_content_moderation: should_skip}) | ||
raise NotFound, "channel `#{channel_id}` not found" if update_content_moderation == should_skip |
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 don't think this works the way you describe it (there's no test for it) and I'm not sure we want to do it anyway.
Sequel::Dataset#update returns the number of rows updated, not the new value; therefore update_content_moderation == should_skip
is probably never true becase it's comparing an integer to a boolean.
It's generally a good idea to make server APIs idempotent (I enjoyed this article on the subject). In this case, that means we want this method to do nothing and report success if the setting is already the desired value, rather than throw an error. This way, if a client requests this operation but the server's response gets lost on the way back, the client can safely re-send the request and not worry about causing an error.
# @raise [NotFound] if the channel does not exist or already has the desired | ||
# value for skip_content_moderation | ||
# | ||
def change_content_moderation(channel_id, should_skip) |
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.
Naming nit: set
instead of change
. Since this is a boolean value it might be a non-issue, but with objects it matters. "Change" implies the subject may inherit some of its past state, while "set" suggests the value after the operation will be only the new information you passed it. It's like the difference between PUT
and PATCH
in HTTP.
row = @table.where(id: id).exclude(state: 'deleted').first | ||
raise NotFound, "channel `#{channel_id}` not found" unless row | ||
|
||
update_content_moderation = @table.where(id: id).exclude(state: 'deleted').update({skip_content_moderation: should_skip}) |
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.
Because update
is safe if the project is not present and it returns the number of rows modified, you can implement this API with one query instead of two.
_owner, id = storage_decrypt_channel_id(channel_id)
rows_changed = @table.
where(id: id).
exclude(state: 'deleted').
update({skip_content_moderation: should_skip})
raise NotFound, "channel `#{channel_id}` not found" unless rows_changed > 0
@Erin007 please tag me when you are ready for another look |
…dator permissions
ab55b1c
to
a36f65f
Compare
…or? to access request so current_user is known
7281052
to
35bb84d
Compare
The buttons work! Thank you for your help @davidsbailey and @islemaster. This PR is ready for review. |
@@ -303,6 +303,29 @@ def test_abuse | |||
|
|||
# Ideally we would also test that deleting abuse works when we're an admin | |||
# but don't currently have a way to simulate admin from tests | |||
# TODO (Erin B) confirm if this is accurate and update tests if needed. | |||
# You might not need to be an admin, you might need project validator | |||
# permissions. |
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'm going to tackle this in a follow up PR.
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'd like to see showProjectAdmin.js updated to respect the async nature of the API call. The rest are optional cleanup suggestions.
@@ -83,14 +83,16 @@ export default () => { | |||
} | |||
|
|||
$('#disable-auto-moderation').click(function () { | |||
alert("You clicked the disable automated image moderation button. It doesn't do anything yet because our moderation filter is turned off, but it will be functional soon. Thanks for your patience with this work-in-progress button!"); | |||
dashboard.project.disableAutoContentModeration(); | |||
alert("Our moderation filter is currently turned off for all projects, but your choice will be saved and applied when we re-enable the moderation service."); | |||
$('#disable-auto-moderation').hide(); | |||
$('#moderation-explanation').hide(); | |||
$('#enable-auto-moderation').show(); |
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 would be nice if we didn't switch the displayed buttons until/unless the request to enable/disable moderation succeeds. Also a great opportunity to use async/await!
// in project.js
// Make these methods return a promise that resolves when the request succeeds, or rejects if it fails.
disableAutoContentModeration() {
return new Promise((resolve, reject) => {
channels.update(`${this.getCurrentId()}/disable-content-moderation`, (err) => {
err ? reject(err) : resolve()
});
});
},
// in showProjectAdmin.js
// Make the click handler async, and await disableAutoContentModeration before changing the buttons.
$('#disable-auto-moderation').click(async function() {
await dashboard.project.disableAutoContentModeration();
alert("Our moderation filter...");
$('#disable-auto-moderation').hide();
$('#moderation-explanation').hide();
$('#enable-auto-moderation').show();
});
bad_request | ||
end | ||
{skip_content_moderation: value}.to_json | ||
end |
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 two blocks are identical except for the route name and the boolean value being set. Let's extract a helper method!
shared/middleware/channels_api.rb
Outdated
@@ -283,4 +317,8 @@ class ChannelsApi < Sinatra::Base | |||
post %r{/v3/channels/([^/]+)/abuse/delete$} do |_id| | |||
call(env.merge('REQUEST_METHOD' => 'DELETE', 'PATH_INFO' => File.dirname(request.path_info))) | |||
end | |||
|
|||
def project_validator? |
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.
Possibly worth a comment explaining this pass-through method is here so we can stub it in tests.
@@ -69,15 +69,15 @@ def has_permission?(permission) | |||
end | |||
|
|||
# @param [Integer] section_id | |||
# @returns [Boolean] true iff the current user is the owner of the given section. | |||
# @returns [Boolean] true if the current user is the owner of the given section. | |||
# Note: NOT always true for admins. |
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 isn't a typo - it's shorthand for "if and only if".
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.
But maybe you're right to correct that, given the note on the next line!
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.
ah, I had no idea. Thanks!
|
||
post "/v3/channels/#{channel_id}/enable-content-moderation" | ||
assert last_response.ok? | ||
assert_equal false, JSON.parse(last_response.body)['skip_content_moderation'] |
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.
Paranoid testing suggestion - double each of these tests, so you call disable-content-moderation
twice in a row and enable-content-moderation
twice in a row, verifying that each route is a no-op when the project is already in the desired state, and not just toggling the value.
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 updated the test coverage and implemented async await, anything else pressing before merging @islemaster? |
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.
Looks great!
To make the buttons in #23406 function so that project validators can enable or disable automated content moderation for a given project, this PR introduces methods to access and update the value of
skip_content_moderation
. Also included are tests for the new methods.