gutenberg-mobile
gutenberg-mobile copied to clipboard
TT | 3471 | "Introduce allow-list and disallow-list for blocks."
Description
This is meant to be treated as a draft
.
Idea
The general idea behind this work is that there is a desire to allow the Gutenberg Block Editor to restrict the block types it offers in various contexts it is presented in to the customer. It came from this feature request.
The changes found here should, in theory, only impact the blocks presented in the block picker, and not affect the blocks displayed within the editor if someone were to manually modify the markup.
The "requirements" that I thought seemed reasonable to me would be:
- If you pass in neither an allow list nor a disallow list, then proceed by including all registered blocks, i.e.,
core/
,jetpack/
, or otherwise. - If you pass in an allow list, and not a disallow list, then proceed by only including what is in the allow list from the entire set of registered blocks, i.e.,
core/
,jetpack/
, or otherwise. - If you pass in a disallow list, and not an allow list, then proceed by including the entire set of registered blocks, i.e.,
core/
,jetpack/
, or otherwise, sans the blocks in the disallow list. - If you pass in both an allow and disallow list, then proceed by ignoring the disallow list and and only including the blocks that result from the allow list detailed in
1
(as it is the more restrictive case).- However, this use case isn't realistic in my opinion. The consumer should only provide an allow or disallow list whichever is more convenient for the context.
- An allow list is preferred when the amount of blocks we'd like to present to the customer is small.
- A disallow list is preferred when the amount of blocks we'd like to present is large.
Notes
Previous Attempt
I made a previous attempt to implement the concept of allowing and disallowing block types. I tried ensuring that registration of the disallowed blocks never occured using registerBlockType
if the block name wasn't included the provided allow list, or if the block name was provided in the disallow list.
This attempt had some unintended side-effects. These are detailed in this P2 post if you would like more context.
Remaining Work
- [ ] Document the interfaces with JSDoc and include useful contextual knowledge.
- [x] Automate tests to reflect the requirements defined here (since it seems that this approach is okay to move forward with. I may still need some help understanding the best possible testing strategy from a functional or integration test perspective as opposed to basic unit testing.)
- [x] Consider adding a comment in the modified test explaining the reason for using the inline require (as noted by @fluiddot).
- [ ] Refine the PR description for the current approach and update the testing instructions.
- [ ] Update PR to "Ready for Review" when the PR description and testing is "ready."
- [x] Change implementation to ensure
hideBlocks
gets ignored ifshowBlocks
andhideBlocks
are provided together (if that makes sense).
Demo
Providing empty sets of showBlocks and hideBlocks . |
Providing only the jetpack/contact-info block type in the showBlocks set while capable . |
---|---|
![]() |
![]() |
Providing only the jetpack/contact-info block type in the hideBlocks set while capable . |
Providing only the jetpack/contact-info block type in the showBlocks set while uncapable . |
---|---|
![]() |
![]() |
To test: TBD
PR submission checklist: TBD
- [x] I have considered adding unit tests where possible.
- [ ] I have considered if this change warrants user-facing release notes more info and have added them to RELEASE-NOTES.txt if necessary.
Wanna run full suite of Android and iOS UI tests? Click here and 'Approve' CI job!
I made these changes as noted in my commit log, @fluiddot . I would appreciate your feedback. I tried to explain my rationale behind the changes in our other thread, but I can bring that to the PR if it's easier to track. I'm also not opposed to trying a different approach to the implementation. This approach just appears to work well.
-
src/allowed-blocks-setup.js
- Added
jetpack/layout-grid
as an "Available Jetpack Block." However, it's availability is irrelevant since it is not registered withregisterJetpackBlock
but ratherregisterBlockType
. - Added a centralized entry point for registration and hiding of block types called
setupBlocks
that should be executed in thegutenberg-mobile
native.render
action as early as possible in the editor initialization. - Removed the redundant dispatching of the
SHOW_BLOCK_TYPES
action for the inverse of the block types that are hidden with the dispatching of theHIDE_BLOCK_TYPES
action to @fluiddot 's point. - Ensured that all
jetpack/
block types export their ownregisterBlock
entry point function to make registration conditional for non-core/
block types. - Added
registerJetpackBlocksIfCapable
to ensure thatjetpack/contact-info
,jetpack/layout-grid
, andjetpack/story
are only registered if their respectivecapabilities
passed through the editor intialprops
indicate they should be enabled for the customer. - Simplified the registration of all
jetpack/
block types previously done insetupJetpackEditor
andsetJetpackData
in the newsetupJetpackBlocks
function. ThesetTimeout
to ensurejetpack/
blocks are conditionally hidden on next tick is no longer needed if not attempting to select thecapabilities
from thecore/block-editor
store, but rather just leverage theprops
passed directly into the initialization of the editor. The inlinerequire
is no longer needed since it now exports functions used to register lazily as opposed to implied registration when the module is loaded.toggleBlock
is also no longer needed as the hiding of all block types,core/
,jetpack/
, or future types has been unified. - Removed the unnecessary
dependencies
argument from thesetupAllowedBlocks
function signature. We can just leverage Jest module mocking for dependency injection when necessary. - Removed the unnecessary
setTimeout
from thesetupAllowedBlocks
function as it no longer has to wait forjetpack/
block types to be hidden first since that flow can now be done on the current tick of the event loop instead of a future tick waiting for the redux store state to have changed.
- Added
-
src/block-experiments-setup.js
- Removed the module to consolidate block type registration and hiding into
src/allowed-blocks-setup.js
.
- Removed the module to consolidate block type registration and hiding into
-
src/block-support/supported-blocks.json
- Added
jetpack/layout-grid
andjetpack/story
to the list of supported blocks. This appears to only be used in the iOS Swift React Native Bridge.
- Added
-
src/index.js
- Consolidated the entry points used for registering and hiding
jetpack/contact-info
,jetpack/layout-grid
, andjetpack/story
tosetupBlocks
fromsetupJetpackEditor
andsetupBlockExperiments
.
- Consolidated the entry points used for registering and hiding
-
src/jetpack-editor-setup.js
- Removed the module to consolidate block type registration and hiding into
src/allowed-blocks-setup.js
.
- Removed the module to consolidate block type registration and hiding into
-
src/test/index.js
- Updated the staging of the existing test that ensures
jetpack/
block types register successfully during the editor initialization.
- Updated the staging of the existing test that ensures
Thanks @ttahmouch for making the changes on the PR 🙇.
You're welcome, @fluiddot . Thank you for being so patient through this process. 🙏
I've added some comments in the code but I think that the PR is ready to be reviewed so please when you're available, let me know if you could change it to "Ready for review". It would be also nice if you could also wrap up the PR by explaining the current approach and provide some testing instructions for reviewers to use as a guide.
Thanks for adding comments throughout the code. I appreciate it. I didn't feel comfortable changing its status from draft
until I knew that this approach was going to be the one we intend to move forward with. I didn't want to add unnecessary automated tests for an approach that may not realistically be used.
I will try to make the PR description clearer with respect to the current approach and update the testing instructions. Apologies if it was confusing to anyone.
From my side, I went ahead and tested the PR and except for a couple of issues everything is working fine 🎊 , here are the insights:
- I tested disabling some of the capabilities used in the Jetpack blocks and including the disabled blocks in the allow-list and I confirmed that they weren't displayed ✅ .
- I tested the requirements described in the PR's description and they were fulfilled except one, here are the details:
If you pass in neither an allow list nor a disallow list, then proceed by using all core blocks.
This works as expected ✅ .
If you pass in an allow list, and not a disallow list, then proceed by only including what is in the allow list from the entire set of core blocks.
This works as expected ✅ .
If you pass in a disallow list, and not an allow list, then proceed by including the entire set of core blocks sans the blocks in the disallow list.
This works as expected ✅ .
Thank you for testing and providing feedback regarding your insights. 🙏 🙇
I'm glad these cases worked as intended.
If you pass in both an allow and disallow list, then proceed by removing blocks named in the disallow list first, then keeping the blocks named in the allow list second.
For this case, I included the same block in both allow/disallow lists and I noticed that is not displayed so looks like that this requirement is not being fulfilled ❌.
I used the following values:
showBlocks: [ 'core/paragraph' ], hideBlocks: [ 'core/paragraph' ],
The reason this is no longer working as intended I believe is simply because I am no longer dispatching the SHOW
action as the PR once was. So it never removes the core/paragraph
block type from the hide list in the store state in your example.
That being said this has always been the use case that has never made particularly intuitive sense to me. If the consumer says "I want you to take the set of all registered block types, reduce it by hiding a few of them, and then reduce it further by only showing a few of them" it would likely make practical sense for either:
- the
hideBlocks
to just go ignored altogether and only acknowledge theshowBlocks
since it is more restrictive. - taking a stricter stance by simply not allowing both
hideBlocks
andshowBlocks
to be provided in unison.
I'd lean in favor of the former over the latter. I do think that ignoring the hideBlocks
and letting showBlocks
supersede actually makes sense. WDYT?
It really only matters in your edge case that they're providing the same block type in both sets.
Practical Example?
// Registered Blocks [ 'core/audio', 'core/paragraph', 'core/video' ]
// First, reduce set to [ 'core/audio', 'core/video' ].
// Then, reduce remaining set to [ 'core/audio' ].
hideBlocks: [ 'core/paragraph' ],
showBlocks: [ 'core/audio' ],
Impractical Example?
// Registered Blocks [ 'core/audio', 'core/paragraph', 'core/video' ]
// First, reduce set to [ 'core/audio', 'core/video' ].
// Then, reduce remaining set to [ 'core/paragraph' ]?
hideBlocks: [ 'core/paragraph' ],
showBlocks: [ 'core/paragraph' ],
- [ ] I will try to make the PR description clearer with respect to the current approach and update the testing instructions.
- [ ] I will try to update the automated tests since it seems that this approach is okay to move forward with. (I may still need some help understanding the best possible testing strategy from a functional or integration test perspective as opposed to basic unit testing.)
- [ ] I will change it to "Ready for Review" when I add automated testing and update the description.
- [ ] I will make the change to ensure
hideBlocks
gets ignored ifshowBlocks
andhideBlocks
are provided together (if that makes sense).
Thanks for adding comments throughout the code. I appreciate it. I didn't feel comfortable changing its status from
draft
until I knew that this approach was going to be the one we intend to move forward with. I didn't want to add unnecessary automated tests for an approach that may not realistically be used.I will try to make the PR description clearer with respect to the current approach and update the testing instructions. Apologies if it was confusing to anyone.
No worries, ok, it makes sense, let's change it to "ready for review" once the PR is updated 👍 .
The reason this is no longer working as intended I believe is simply because I am no longer dispatching the
SHOW
action as the PR once was. So it never removes thecore/paragraph
block type from the hide list in the store state in your example.That being said this has always been the use case that has never made particularly intuitive sense to me. If the consumer says "I want you to take the set of all registered block types, reduce it by hiding a few of them, and then reduce it further by only showing a few of them" it would likely make practical sense for either:
- the
hideBlocks
to just go ignored altogether and only acknowledge theshowBlocks
since it is more restrictive.- taking a stricter stance by simply not allowing both
hideBlocks
andshowBlocks
to be provided in unison.I'd lean in favor of the former over the latter. I do think that ignoring the
hideBlocks
and lettingshowBlocks
supersede actually makes sense. WDYT?
Yeah, since providing both lists is an edge case, I think ignoring hideBlocks
in case showBlocks
is defined makes sense. One thing it would be great is to add a comment in the code explaining this behavior to prevent potential misleading, wdyt?
- [ ] I will try to update the automated tests since it seems that this approach is okay to move forward with. (I may still need some help understanding the best possible testing strategy from a functional or integration test perspective as opposed to basic unit testing.)
If you're referring to UI tests with "automated tests", not sure if we'll be able to cover this feature with them because, as far as I know, the initial props can't be set on this type of test.
Yeah, since providing both lists is an edge case, I think ignoring hideBlocks in case showBlocks is defined makes sense. One thing it would be great is to add a comment in the code explaining this behavior to prevent potential misleading, wdyt?
Is this maybe highlighting that we could be modeling this a bit better? Did we consider not having two lists and instead just providing one with a show/hide toggle (this might have been what you meant @ttahmouch when you suggested "taking a stricter stance by simply not allowing both hideBlocks and showBlocks to be provided in unison")? I'm thinking of something like:
{
blocks: [string],
blockAction: "disallow" | "onlyAllow"
}
Feels like that would both make the behavior more transparent and simplify the implementation. I apologize if you already considered this, and I just missed it. I do realize this PR is in a relatively late stage of revision, and I am fine with the ignore-one-of-the-lists-if-both-are-defined since it sounds like that is what you prefer.
If you're referring to UI tests with "automated tests", not sure if we'll be able to cover this feature with them because, as far as I know, the initial props can't be set on this type of test.
I was referring to mostly if we could test the integration of the registration flow with the hiding flow instead of just unit testing the individual functions in isolation. I usually prefer to integration test, or higher-level functional test, over isolated unit tests with everything mocked since it more accurately reflects the tested use cases from the customers' user experience. It's just a personal preference.
To be pedantic, I don't even necessarily mean functional testing as "running the application and interacting with the visual elements on the screen from a 'black box'" with something like Selenium/Appium/Espresso/XCUITest. Even something that simulates testing like that from the component layer, e.g., @testing-library/react-native
, would apply.
Does this make sense, @fluiddot ?
Yeah, since providing both lists is an edge case, I think ignoring hideBlocks in case showBlocks is defined makes sense. One thing it would be great is to add a comment in the code explaining this behavior to prevent potential misleading, wdyt?
Is this maybe highlighting that we could be modeling this a bit better? Did we consider not having two lists and instead just providing one with a show/hide toggle (this might have been what you meant @ttahmouch when you suggested "taking a stricter stance by simply not allowing both hideBlocks and showBlocks to be provided in unison")? I'm thinking of something like:
{ blocks: [string], blockAction: "disallow" | "onlyAllow" }
Feels like that would both make the behavior more transparent and simplify the implementation. I apologize if you already considered this, and I just missed it. I do realize this PR is in a relatively late stage of revision, and I am fine with the ignore-one-of-the-lists-if-both-are-defined since it sounds like that is what you prefer.
It's not my "preference." I did think that intuitively reading the interface as show
or hide
would be more descriptive as to the intent behind the property when initializing the editor. I do agree that having a single list would simplify the implementation details of the block hiding flow, but it also just felt like a micro-optimization for a use case that I didn't think would ever practically exist. It's just something that I thought would make a good case for explaining in a JSDoc.
I am not opposed to changing the interface for the benefit of the consumer of the block editor. I just don't feel strongly that the approach using a single list would necessarily make the interface more intuitive since practically speaking the use case is not realistic.
If the consumer only wanted to show a few blocks, then they should prefer the showBlocks
interface. If the consumer only wanted hide a few blocks, then they should prefer the hideBlocks
interface. Realistically, providing both sets is effectively the same as providing the more restrictive of the two.
Does that make sense, @mchowning ?
Aside
If we did attempt it with an approach similar to {blocks: [], blockAction: ""}
, then I think we'd have to also handle a different edge case or two. We'd realistically want to have the default blockAction
be undefined
or "default"
, or however we'd name it, to indicate that we'd neither like to allow nor disallow a subset of blocks, but what happens if a blocks
set is still provided? Do we ignore it (treat it as empty)? Or do we default the blockAction
to an action other than "default"
, e.g., "onlyAllow"?
It may just be shifting the concern? Does that make sense?
To be clear, I like the concept and appreciate your feedback. I'm happy to make changes if the way that I've been implementing it so far is not particularly intuitive. I just took a first pass at it as a draft. It wasn't necessarily something I was married to and intending to be my final pass.
I am not opposed to changing the interface for the benefit of the consumer of the block editor. I just don't feel strongly that the approach using a single list would necessarily make the interface more intuitive since practically speaking the use case is not realistic.
What you say makes sense, and I'm totally fine sticking with the current object structure. I personally like a single list a bit better, but I think that this is something that is firmly in the realm of personal preference (and your approach is probably the more javascript-y™ one), so let's stick with it.
If you're referring to UI tests with "automated tests", not sure if we'll be able to cover this feature with them because, as far as I know, the initial props can't be set on this type of test.
I was referring to mostly if we could test the integration of the registration flow with the hiding flow instead of just unit testing the individual functions in isolation. I usually prefer to integration test, or higher-level functional test, over isolated unit tests with everything mocked since it more accurately reflects the tested use cases from the customers' user experience. It's just a personal preference.
To be pedantic, I don't even necessarily mean functional testing as "running the application and interacting with the visual elements on the screen from a 'black box'" with something like Selenium/Appium/Espresso/XCUITest. Even something that simulates testing like that from the component layer, e.g.,
@testing-library/react-native
, would apply.Does this make sense, @fluiddot ?
It makes totally sense, I just wanted to make sure that we were talking about integration tests and not UI tests, thanks for the clarification 👍 .
I like the idea to use @testing-library/react-native
, in fact, we recently added a guideline for integration tests using this library (reference) but it's meant to be used in the tests defined in the Gutenberg repository only. However, if you would like to use it, some time ago I worked on a PR that enable them in Gutenberg Mobile, so feel free to take those changes in case they help you in this purpose.
Being this said, I foresee that the integration tests might take extra time so maybe we could add them later on and in the meantime address the rest of the subtasks:
- [ ] I will try to make the PR description clearer with respect to the current approach and update the testing instructions.
- [ ] I will make the change to ensure
hideBlocks
gets ignored ifshowBlocks
andhideBlocks
are provided together (if that makes sense).
What do you think?
Being this said, I foresee that the integration tests might take extra time so maybe we could add them later on and in the meantime address the rest of the subtasks:
- [ ] I will try to make the PR description clearer with respect to the current approach and update the testing instructions.
- [ ] I will make the change to ensure
hideBlocks
gets ignored ifshowBlocks
andhideBlocks
are provided together (if that makes sense).What do you think?
That's totally reasonable, @fluiddot .
I finally had a few minutes to think about what tests I intend to write, and this is a crude list of them just in case you'd like to chime in. I hope they make sense since I wrote them pretty quickly.
Integration Tests
Given native.render
hook is emitted,
When setupBlocks
takes place,
Then ...
- Ensure that the
jetpack/layout-grid
block is registered if capable. - Ensure that the
jetpack/layout-grid
block is not registered if incapable. - Ensure that the
jetpack/story
block is registered if capable. - Ensure that the
jetpack/story
block is not registered if incapable. - Ensure that the
jetpack/contact-info
block is registered if capable. - Ensure that the
jetpack/contact-info
block is not registered if incapable. - Ensure that Jetpack Data is set if Jetpack is Active.
- Ensure that Jetpack Data is not set if Jetpack is Inactive.
- Ensure that the
HIDE
action is not dispatched if neitherhideBlocks
norshowBlocks
types are provided. - Ensure that the
HIDE
action is dispatched with hide types ifhideBlocks
types are provided andshowBlocks
types are not provided. - Ensure that the
HIDE
action is dispatched with inverse show types ifshowBlocks
types are provided andhideBlocks
types are not provided. - Ensure that the
HIDE
action is dispatched with inverse show types ifshowBlocks
types are provided andhideBlocks
types are provided.