-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
feat!: Invalid Blocks #7958
feat!: Invalid Blocks #7958
Conversation
There are a bunch of places in the code that look like:
or even:
It might be worth adding another helper method or two that establishes a simpler pattern for these, but I'm not sure what to name to use, or whether to try combining these methods somehow. |
High-level question before I review the rest of this: Instead of having the |
Yes! We would just have to decide where we want to draw line on backward compatibility. Do we still keep the isEnabled and setEnabled methods (in which case they would be internally implemented as setting an invalid reason)? Do we keep the XML and JSON attributes related to disabling blocks? If we completely remove the old isEnabled system, should the new system continue to be called isValid or should it be renamed to take the place of isEnabled? Of course, the least disruptive thing to do would be to keep the old isEnabled API even if we implement it differently. |
I think it's actually disruptive either way, which is good for us! We're changing the semantics of
Mmm the serialization is something else I'm a bit concerned about with this proposal. I don't want to make our saves larger if we don't have to. E.g. the behavior of the disable orphans change listener is completely deterministic based on other state of the workspace, and shouldn't need to be saved separately. If we used an overridable function instead of the invalid reasons map, we wouldn't have to worry about serialization. I know we talked about using an overridable function, but can't remember what the problem was with that. Do you have context? |
The main reason why I ended up not going with an overridable function is because I couldn't figure out a system to make sure that the function actually gets called whenever you need it to be called. I guess the simplest thing to do would be to call it on all blocks whenever there's any change, but that felt like it might be too heavy. Aside from that, how do you know whether something changed that affects whether a block should be invalid, especially for plugins which may have implied dependencies we couldn't predict? In the core library, procedure call blocks are an example of an indirect dependency: they become valid or invalid depending on changes to a different block with no direct connection between the two. How would we make sure that the validity status of call blocks gets updated when the procedure definition block is modified? I mean, we could add a special case for that, similar to our existing event listeners, but then plugin authors would have to deduce whether their dependency is one that's already covered by the default behavior or something that they too will have to special-case and possibly getting confused when they expect one but get the other. I figured it's better to just keep the existing paradigm of writing your own event listeners that explicitly encode the dependencies. On a side note, there is a way to automatically update things based on implied dependencies that works reasonably well: data binding aka signals: https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob |
Sweet sweet, makes sense :D Thank you for the context! |
Ok how about this: I think it makes sense to just have the one set of I don't think unifying them causes any more behavioral changes than keeping them separate. Either way, the "disabledness" of a block is dependent on more things than just a single boolean, so someone's behavior could change if they were previously using In this proposal, Wrt serialization, I think it makes sense to continue deserializing the Does that make sense? And how does that sound to you? |
I've attempted to merge the old and new systems in a way that's not too disruptive. The There was a private "disabled" property that was nevertheless in use in various places. I updated the references I found in the core library, demos, and tests. I also replaced the property with private getter and setter methods to manipulate the MANUALLY_DISABLED reason, which are also marked as deprecated. |
@johnnesky Stop working while you're OOO :P |
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 looks great John =) Thank you for your work on this! And for adding tests :DD Just a few nits.
@@ -1489,6 +1489,12 @@ Blockly.Msg.PROCEDURES_BEFORE_PARAMS = 'with:'; | |||
/// function with parameters]. | |||
Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS = 'with:'; | |||
/** @type {string} */ | |||
/// warning - This appears if a block that runs a function can't run because the function | |||
/// definition block is disabled. See | |||
/// [https://blockly-demo.appspot.com/static/demos/code/index.html#q947d7 this sample of a |
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.
@maribethb We're planning to continue supporting cloud storage for the code demo right?
0f9fe46
to
dd1ff73
Compare
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! Make whatever decision you want wrt the event param and then you can go ahead and merge! (assuming you still have perms, if not lmk)
The basics
The details
Resolves
Fixes #1392 and fixes #1390
It also helps address the blockly-samples issue google/blockly-samples#2035 for core procedures. It'll need to be addressed separately for blockly-samples procedures after this change is published.
Proposed Changes
This adds new methods to blocks similar to
isEnabled
andsetEnabled
, calledisValid
andsetInvalidReason
. The former are now intended exclusively for user manipulation via the UI, and the latter are intended for automatic processes. The latter supports adding multiple independent reasons why a block should be considered invalid, and the block is only valid if no reasons are present. A block is only included in code generators if it is both enabled and valid.Reason for Changes
This allows mixing features like having a continue block outside of a loop, an orphan block not attached to a root block, and a user intentionally disabling a block, without any of these features interfering the others. Previously, the disableOrphans could not be combined with the user block disabling feature or with built-in blocks that disable themselves, and the user block disabling feature did not always work well with these built-in blocks because a user could re-enable a block that had been automatically disabled even though the conditions that disabled it haven't changed.
Test Coverage
I updated tests related to automatic disabling of blocks and added similar tests for the control flow block.
Documentation
We should update the codelab at: https://blocklycodelabs.dev/codelabs/validation-and-warnings/index.html
I don't think we have much other documentation of the setEnabled method but maybe we could add some as well as documentation of the new feature at: https://developers.google.com/blockly/guides/create-custom-blocks/define-blocks#per-block_configuration
Additional Information
In the process of testing this, I ran across a couple existing bugs that are not addressed by this PR:
#7950
#7951
Breaking changes / To fix
The
block.setEnabled(...)
method is now deprecated in favor ofblock.setDisabledReason(...)
which allows you to indicate a reason why the block should or should not be disabled. Theblock.setEnabled(...)
still exists, but only affects the"MANUALLY_DISABLED"
reason (which is the same one that the right-click context menu option affects). If a block is disabled for other reasons, such as being detached from a required parent block, then calling theblock.setEnabled(...)
method cannot fully re-enable the block anymore. You can callgetDisabledReasons()
to get a list of all of the reasons why a block is currently disabled.The XML and JSON serialization formats also have deprecations. The XML attribute
disabled
is now deprecated in favor ofdisabled-reasons
and the JSON propertyenabled
is now deprecated in favor ofdisabledReasons
.