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 new rule parser package #65171

Closed
wants to merge 11 commits into from
Closed

Add new rule parser package #65171

wants to merge 11 commits into from

Conversation

senadir
Copy link
Contributor

@senadir senadir commented Sep 9, 2024

What?

This PR introduces a new package @wordpress/rule-parser that's meant to evaluate logical, array based rules, into a boolean.

Why?

The main usage for this package would be to solve a couple of problems:

  • Giving developers a unified API to control visibility in the DataForms (cc @joshuatf).
  • Provide an API to bring in the main functionality of https://blockvisibilitywp.com/ into Gutenberg (cc @ndiego ).
  • Give plugin authors a unified API and structure to implement rules for their developers, the main case I'm building this for is to have developers show/hide fields in Checkout depending on customer context as well as accept values depending on that.

The API

Currently, this API only covers the JS side, but a following PHP should be added once we settle on how this one works, a big requirement for this is that it developers write the logic once, and that evaluates to the same value in both JS and PHP.

Note

This package is only meant to be used by implementors, not direct developers. implementors are then responsible for collecting rules from developers or users, either via UI (like Blocks Visibility), or code (like DataForms and Checkout), evalute the rules against their provided context, and use the outcome however they see fit.

The package accepts rules in the shape of [ source, operator, target ] and can be infinitely nested on a variation of combinators, that resemble || (OR, ANY) or && (AND, ALL).

Examples of rules

  1. Simple rule
const rule = [
    [ 'user.role', 'is', 'editor' ]
];
  1. Array of rules
const rules = [
    [ 'user.role', 'is', 'editor' ],
    [ 'post.categories', 'contain', 'tutorials' ]
];
  1. Array of rules with explicit combinator. If no combinator is set, ALL will be used.
const rules = [
    'ALL',
    [
        [ 'user.role', 'is', 'editor' ],
        [ 'post.categories', 'contain', 'tutorials' ]
    ]
];
  1. Nested arrays
const rules = [
    'ANY',
    [
        [ 'user.role', 'is', 'editor' ],
        [ 'post.categories', 'contain', 'tutorials' ],
        [ 
            'ALL',
            [
                [ 'user.id', 'in', [ 1, 2, 3 ] ],
                [ 'post.blocks', 'not contain', 'core/embed' ]
            ]
        ]
    ]
];

Anatomy of a rule

A rule is made of 3 values, a source, an operator, and a target.

  • Source: which start as a rawSource (a string) that will be evaluated to a primitive (string, number, boolean) or an array of primitives, evaluation is done by using the context that the implementor provides.
  • Operator: a function that compares an evaluated source to a target value, this package ships with 10 operator, and provides the ability to alias them as well as providing new operators.
  • Target: a primitive (string, number, boolean) or an array of primitives (all the same type), targets are static at code/UI level, and should be considered static regardless of session and place (PHP or JS).

Along of combinators, an array of rules can be transformed to a single boolean.

Usage

import { parser } from "@wordpress/rule-parser";

const context = {
    'user.id': 1.
    'user.role': 'admin',
    'post.categories': [ 'tutorials' ],
    'post.blocks': [ 'core/paragraph', 'core/heading', 'woocommerce/checkout' ]
};

const rules = [
    'ANY',
    [
        [ 'user.role', 'is', 'editor' ],
        [ 'post.categories', 'contain', 'tutorials' ],
        [ 
            'ALL',
            [
                [ 'user.id', 'in', [ 1, 2, 3 ] ],
                [ 'post.blocks', 'not contain', 'core/embed' ]
            ]
        ]
    ]
];

const result = parser( rules, context );

console.log( result ); // true

Context doesn't have to be static and predefined, and I think we can also functions that gets called at evaluation time.

Reactivity

The package currently isn't reactive, it means you need to recall parser each time context changes up, I'm still torn between handling this at package level (by introducing a hook that allows providing dependencies) or leaving it up for implementors.

Operators

The package ships with a simple registry that allows you register a new operator or an alias to an operator. This should allow implementor to specify which rules they want to support, and even add additional ones that aren't covered by core.

import { parser, registry } from '@wordpress/rule-parser';

registry.register( 'between', ( source, target, rule ) => {
	if ( typeof source !== 'number' ) {
		throw new TypeError( 'Source must be of number' );
	}

	if ( ! Array.isArray( target ) || target.length !== 2 ) {
		throw new TypeError( 'Target must be an array of 2 numbers.' );
	}

	const [ min, max ] = target;

	if ( typeof min !== 'number' || typeof max !== 'number' ) {
		throw new TypeError( 'Target must be an array of 2 numbers.' );
	}

	if ( min >= max ) {
		throw new TypeError( 'Min must be less than max.' );
	}

	return source >= min && source <= max;
} );

registry.alias( '<>', 'between' );

parser( [ [ 'cart.totals', '<>', [ 50, 100 ] ], { 'cart.totals': 75 } ); // true.

Why Arrays and not other types of logic.

Before reaching this shape, I evaluated several options, I will go over them here:

Writing regular functions

One option would be to write a regular PHP and JS functions that just evaluates to a boolean, skipping the need to go over an abstraction that eventually does that for you.

The main issue with that is portability and duplication of logic. For developers, they will need to write the same logic twice, which is in itself not a huge issue. But the main issue is portability, for us, we want developers to have their logic apply for the current website, and any external site consuming the API, including external Checkout services, and you can't send or trust JS over the wire.

Writing eval-able pseudo code.

This option involves writing JS code that gets evaluated by a custom lexical parser. WooCommerce new product editor uses (described in this @woocommerce/expression-evaluation package).

The benefit of this approach is that you write a single string instead of arrays, but the drawback is the need to have 2 parsers (in JS and PHP), which is in itself not a huge lift.
The main concern is simply that lack of already structured arrays reduces the ability to better type and validate them.

Internally in Woo, and after some discussions, we decided this is not the approach we want to continue with.

N-item arrays

Block Visibility plugin uses a similar approach, but instead of having 3 items arrays, it can have several items, being for example a general selector post and a subSelector of postId.

I initially considered this approach but decided it's a bit unpredictable for implementor and authors to know, and opted to have dot notation, thou not forced. Implementor can decide how their system translates back to this notation.

JSON objects

Going the same route as above, but instead of 3 items arrays, you use JSON objects to describe the rule, this is the route that WooCommerce's remote notification system works (by sending a note and a collection of rules with it to decide if it should be shown or not), described in this document.

I don't have strong feelings about this, but I consider writing JSON objects with the same keys a bit too verbose for this goal, given how well defined the array shape is.

Next steps and remaining tasks

  • Write up documentation for this package, with more examples.
  • Mark the package as experimental, and publish it via NPM only.
  • Write the same parser in PHP.
  • Include the package in the wp global to be considered stable.

I'm tagging some people who can help review this and validate the approach.
@youknowriad @oandregal @ndiego @joshuatf

@senadir senadir added the [Type] New API New API to be used by plugin developers or package users. label Sep 9, 2024
@senadir senadir self-assigned this Sep 9, 2024
Copy link

github-actions bot commented Sep 9, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @joshuatf.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: joshuatf.

Co-authored-by: senadir <assassinateur@git.wordpress.org>
Co-authored-by: kevin940726 <kevin940726@git.wordpress.org>
Co-authored-by: sirreal <jonsurrell@git.wordpress.org>
Co-authored-by: talldan <talldanwp@git.wordpress.org>
Co-authored-by: ntsekouras <ntsekouras@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link

github-actions bot commented Sep 9, 2024

Size Change: +1.25 kB (+0.07%)

Total Size: 1.77 MB

Filename Size Change
build/rule-parser/index.min.js 1.25 kB +1.25 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size
build-module/block-library/file/view.min.js 447 B
build-module/block-library/image/view.min.js 1.78 kB
build-module/block-library/navigation/view.min.js 1.16 kB
build-module/block-library/query/view.min.js 743 B
build-module/block-library/search/view.min.js 616 B
build-module/interactivity-router/index.min.js 2.8 kB
build-module/interactivity/debug.min.js 16.6 kB
build-module/interactivity/index.min.js 13.3 kB
build/a11y/index.min.js 951 B
build/annotations/index.min.js 2.26 kB
build/api-fetch/index.min.js 2.32 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 579 B
build/block-directory/index.min.js 7.11 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/content-rtl.css 4.65 kB
build/block-editor/content.css 4.64 kB
build/block-editor/default-editor-styles-rtl.css 394 B
build/block-editor/default-editor-styles.css 394 B
build/block-editor/index.min.js 258 kB
build/block-editor/style-rtl.css 16.2 kB
build/block-editor/style.css 16.2 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 149 B
build/block-library/blocks/audio/editor.css 151 B
build/block-library/blocks/audio/style-rtl.css 132 B
build/block-library/blocks/audio/style.css 132 B
build/block-library/blocks/audio/theme-rtl.css 134 B
build/block-library/blocks/audio/theme.css 134 B
build/block-library/blocks/avatar/editor-rtl.css 115 B
build/block-library/blocks/avatar/editor.css 115 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/button/editor-rtl.css 265 B
build/block-library/blocks/button/editor.css 265 B
build/block-library/blocks/button/style-rtl.css 538 B
build/block-library/blocks/button/style.css 538 B
build/block-library/blocks/buttons/editor-rtl.css 291 B
build/block-library/blocks/buttons/editor.css 291 B
build/block-library/blocks/buttons/style-rtl.css 328 B
build/block-library/blocks/buttons/style.css 328 B
build/block-library/blocks/calendar/style-rtl.css 240 B
build/block-library/blocks/calendar/style.css 240 B
build/block-library/blocks/categories/editor-rtl.css 132 B
build/block-library/blocks/categories/editor.css 131 B
build/block-library/blocks/categories/style-rtl.css 152 B
build/block-library/blocks/categories/style.css 152 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 121 B
build/block-library/blocks/code/style.css 121 B
build/block-library/blocks/code/theme-rtl.css 122 B
build/block-library/blocks/code/theme.css 122 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 420 B
build/block-library/blocks/columns/style.css 420 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 124 B
build/block-library/blocks/comment-author-avatar/editor.css 124 B
build/block-library/blocks/comment-author-name/style-rtl.css 72 B
build/block-library/blocks/comment-author-name/style.css 72 B
build/block-library/blocks/comment-content/style-rtl.css 120 B
build/block-library/blocks/comment-content/style.css 120 B
build/block-library/blocks/comment-date/style-rtl.css 65 B
build/block-library/blocks/comment-date/style.css 65 B
build/block-library/blocks/comment-edit-link/style-rtl.css 70 B
build/block-library/blocks/comment-edit-link/style.css 70 B
build/block-library/blocks/comment-reply-link/style-rtl.css 71 B
build/block-library/blocks/comment-reply-link/style.css 71 B
build/block-library/blocks/comment-template/style-rtl.css 200 B
build/block-library/blocks/comment-template/style.css 199 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 228 B
build/block-library/blocks/comments-pagination/editor.css 217 B
build/block-library/blocks/comments-pagination/style-rtl.css 234 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 832 B
build/block-library/blocks/comments/editor.css 832 B
build/block-library/blocks/comments/style-rtl.css 632 B
build/block-library/blocks/comments/style.css 631 B
build/block-library/blocks/cover/editor-rtl.css 641 B
build/block-library/blocks/cover/editor.css 642 B
build/block-library/blocks/cover/style-rtl.css 1.62 kB
build/block-library/blocks/cover/style.css 1.6 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 86 B
build/block-library/blocks/details/style.css 86 B
build/block-library/blocks/embed/editor-rtl.css 331 B
build/block-library/blocks/embed/editor.css 331 B
build/block-library/blocks/embed/style-rtl.css 419 B
build/block-library/blocks/embed/style.css 419 B
build/block-library/blocks/embed/theme-rtl.css 133 B
build/block-library/blocks/embed/theme.css 133 B
build/block-library/blocks/file/editor-rtl.css 326 B
build/block-library/blocks/file/editor.css 326 B
build/block-library/blocks/file/style-rtl.css 278 B
build/block-library/blocks/file/style.css 279 B
build/block-library/blocks/file/view.min.js 324 B
build/block-library/blocks/footnotes/style-rtl.css 198 B
build/block-library/blocks/footnotes/style.css 197 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 229 B
build/block-library/blocks/form-input/style-rtl.css 342 B
build/block-library/blocks/form-input/style.css 342 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 344 B
build/block-library/blocks/form-submission-notification/editor.css 341 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/form/view.min.js 470 B
build/block-library/blocks/freeform/editor-rtl.css 2.6 kB
build/block-library/blocks/freeform/editor.css 2.6 kB
build/block-library/blocks/gallery/editor-rtl.css 955 B
build/block-library/blocks/gallery/editor.css 958 B
build/block-library/blocks/gallery/style-rtl.css 1.83 kB
build/block-library/blocks/gallery/style.css 1.82 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 333 B
build/block-library/blocks/group/editor.css 333 B
build/block-library/blocks/group/style-rtl.css 103 B
build/block-library/blocks/group/style.css 103 B
build/block-library/blocks/group/theme-rtl.css 79 B
build/block-library/blocks/group/theme.css 79 B
build/block-library/blocks/heading/style-rtl.css 188 B
build/block-library/blocks/heading/style.css 188 B
build/block-library/blocks/html/editor-rtl.css 346 B
build/block-library/blocks/html/editor.css 347 B
build/block-library/blocks/image/editor-rtl.css 785 B
build/block-library/blocks/image/editor.css 787 B
build/block-library/blocks/image/style-rtl.css 1.59 kB
build/block-library/blocks/image/style.css 1.59 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/image/view.min.js 1.65 kB
build/block-library/blocks/latest-comments/style-rtl.css 355 B
build/block-library/blocks/latest-comments/style.css 354 B
build/block-library/blocks/latest-posts/editor-rtl.css 179 B
build/block-library/blocks/latest-posts/editor.css 179 B
build/block-library/blocks/latest-posts/style-rtl.css 509 B
build/block-library/blocks/latest-posts/style.css 510 B
build/block-library/blocks/list/style-rtl.css 107 B
build/block-library/blocks/list/style.css 107 B
build/block-library/blocks/loginout/style-rtl.css 61 B
build/block-library/blocks/loginout/style.css 61 B
build/block-library/blocks/media-text/editor-rtl.css 321 B
build/block-library/blocks/media-text/editor.css 320 B
build/block-library/blocks/media-text/style-rtl.css 558 B
build/block-library/blocks/media-text/style.css 556 B
build/block-library/blocks/more/editor-rtl.css 427 B
build/block-library/blocks/more/editor.css 427 B
build/block-library/blocks/navigation-link/editor-rtl.css 644 B
build/block-library/blocks/navigation-link/editor.css 645 B
build/block-library/blocks/navigation-link/style-rtl.css 192 B
build/block-library/blocks/navigation-link/style.css 191 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 295 B
build/block-library/blocks/navigation-submenu/editor.css 294 B
build/block-library/blocks/navigation/editor-rtl.css 2.19 kB
build/block-library/blocks/navigation/editor.css 2.2 kB
build/block-library/blocks/navigation/style-rtl.css 2.25 kB
build/block-library/blocks/navigation/style.css 2.23 kB
build/block-library/blocks/navigation/view.min.js 1.03 kB
build/block-library/blocks/nextpage/editor-rtl.css 392 B
build/block-library/blocks/nextpage/editor.css 392 B
build/block-library/blocks/page-list/editor-rtl.css 378 B
build/block-library/blocks/page-list/editor.css 378 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 236 B
build/block-library/blocks/paragraph/editor.css 236 B
build/block-library/blocks/paragraph/style-rtl.css 341 B
build/block-library/blocks/paragraph/style.css 340 B
build/block-library/blocks/post-author-biography/style-rtl.css 74 B
build/block-library/blocks/post-author-biography/style.css 74 B
build/block-library/blocks/post-author-name/style-rtl.css 69 B
build/block-library/blocks/post-author-name/style.css 69 B
build/block-library/blocks/post-author/editor-rtl.css 107 B
build/block-library/blocks/post-author/editor.css 107 B
build/block-library/blocks/post-author/style-rtl.css 188 B
build/block-library/blocks/post-author/style.css 189 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 527 B
build/block-library/blocks/post-comments-form/style.css 528 B
build/block-library/blocks/post-content/editor-rtl.css 74 B
build/block-library/blocks/post-content/editor.css 74 B
build/block-library/blocks/post-content/style-rtl.css 79 B
build/block-library/blocks/post-content/style.css 79 B
build/block-library/blocks/post-date/style-rtl.css 62 B
build/block-library/blocks/post-date/style.css 62 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 155 B
build/block-library/blocks/post-excerpt/style.css 155 B
build/block-library/blocks/post-featured-image/editor-rtl.css 729 B
build/block-library/blocks/post-featured-image/editor.css 726 B
build/block-library/blocks/post-featured-image/style-rtl.css 347 B
build/block-library/blocks/post-featured-image/style.css 347 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 399 B
build/block-library/blocks/post-template/style.css 398 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 70 B
build/block-library/blocks/post-time-to-read/style.css 70 B
build/block-library/blocks/post-title/style-rtl.css 226 B
build/block-library/blocks/post-title/style.css 226 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 134 B
build/block-library/blocks/pullquote/editor.css 134 B
build/block-library/blocks/pullquote/style-rtl.css 342 B
build/block-library/blocks/pullquote/style.css 342 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 121 B
build/block-library/blocks/query-pagination-numbers/editor.css 118 B
build/block-library/blocks/query-pagination/editor-rtl.css 154 B
build/block-library/blocks/query-pagination/editor.css 154 B
build/block-library/blocks/query-pagination/style-rtl.css 237 B
build/block-library/blocks/query-pagination/style.css 237 B
build/block-library/blocks/query-title/style-rtl.css 64 B
build/block-library/blocks/query-title/style.css 64 B
build/block-library/blocks/query/editor-rtl.css 452 B
build/block-library/blocks/query/editor.css 451 B
build/block-library/blocks/query/view.min.js 958 B
build/block-library/blocks/quote/style-rtl.css 238 B
build/block-library/blocks/quote/style.css 238 B
build/block-library/blocks/quote/theme-rtl.css 233 B
build/block-library/blocks/quote/theme.css 236 B
build/block-library/blocks/read-more/style-rtl.css 138 B
build/block-library/blocks/read-more/style.css 138 B
build/block-library/blocks/rss/editor-rtl.css 101 B
build/block-library/blocks/rss/editor.css 101 B
build/block-library/blocks/rss/style-rtl.css 288 B
build/block-library/blocks/rss/style.css 287 B
build/block-library/blocks/search/editor-rtl.css 199 B
build/block-library/blocks/search/editor.css 199 B
build/block-library/blocks/search/style-rtl.css 672 B
build/block-library/blocks/search/style.css 671 B
build/block-library/blocks/search/theme-rtl.css 113 B
build/block-library/blocks/search/theme.css 113 B
build/block-library/blocks/search/view.min.js 475 B
build/block-library/blocks/separator/editor-rtl.css 100 B
build/block-library/blocks/separator/editor.css 100 B
build/block-library/blocks/separator/style-rtl.css 248 B
build/block-library/blocks/separator/style.css 248 B
build/block-library/blocks/separator/theme-rtl.css 195 B
build/block-library/blocks/separator/theme.css 195 B
build/block-library/blocks/shortcode/editor-rtl.css 286 B
build/block-library/blocks/shortcode/editor.css 286 B
build/block-library/blocks/site-logo/editor-rtl.css 806 B
build/block-library/blocks/site-logo/editor.css 803 B
build/block-library/blocks/site-logo/style-rtl.css 218 B
build/block-library/blocks/site-logo/style.css 218 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-tagline/style-rtl.css 65 B
build/block-library/blocks/site-tagline/style.css 65 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/site-title/style-rtl.css 206 B
build/block-library/blocks/site-title/style.css 206 B
build/block-library/blocks/social-link/editor-rtl.css 338 B
build/block-library/blocks/social-link/editor.css 338 B
build/block-library/blocks/social-links/editor-rtl.css 757 B
build/block-library/blocks/social-links/editor.css 756 B
build/block-library/blocks/social-links/style-rtl.css 1.51 kB
build/block-library/blocks/social-links/style.css 1.5 kB
build/block-library/blocks/spacer/editor-rtl.css 346 B
build/block-library/blocks/spacer/editor.css 346 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table-of-contents/style-rtl.css 83 B
build/block-library/blocks/table-of-contents/style.css 83 B
build/block-library/blocks/table/editor-rtl.css 394 B
build/block-library/blocks/table/editor.css 394 B
build/block-library/blocks/table/style-rtl.css 640 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 152 B
build/block-library/blocks/table/theme.css 152 B
build/block-library/blocks/tag-cloud/editor-rtl.css 144 B
build/block-library/blocks/tag-cloud/editor.css 144 B
build/block-library/blocks/tag-cloud/style-rtl.css 266 B
build/block-library/blocks/tag-cloud/style.css 265 B
build/block-library/blocks/template-part/editor-rtl.css 368 B
build/block-library/blocks/template-part/editor.css 368 B
build/block-library/blocks/template-part/theme-rtl.css 113 B
build/block-library/blocks/template-part/theme.css 113 B
build/block-library/blocks/term-description/style-rtl.css 126 B
build/block-library/blocks/term-description/style.css 126 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 165 B
build/block-library/blocks/text-columns/style.css 165 B
build/block-library/blocks/verse/style-rtl.css 98 B
build/block-library/blocks/verse/style.css 98 B
build/block-library/blocks/video/editor-rtl.css 396 B
build/block-library/blocks/video/editor.css 397 B
build/block-library/blocks/video/style-rtl.css 192 B
build/block-library/blocks/video/style.css 192 B
build/block-library/blocks/video/theme-rtl.css 134 B
build/block-library/blocks/video/theme.css 134 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.1 kB
build/block-library/common.css 1.1 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.7 kB
build/block-library/editor.css 11.7 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 218 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 15 kB
build/block-library/style.css 15 kB
build/block-library/theme-rtl.css 708 B
build/block-library/theme.css 712 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 52.3 kB
build/commands/index.min.js 16.1 kB
build/commands/style-rtl.css 955 B
build/commands/style.css 952 B
build/components/index.min.js 224 kB
build/components/style-rtl.css 12.1 kB
build/components/style.css 12.1 kB
build/compose/index.min.js 12.7 kB
build/core-commands/index.min.js 2.82 kB
build/core-data/index.min.js 73.2 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.35 kB
build/customize-widgets/style.css 1.35 kB
build/data-controls/index.min.js 641 B
build/data/index.min.js 8.98 kB
build/date/index.min.js 18 kB
build/deprecated/index.min.js 458 B
build/dom-ready/index.min.js 325 B
build/dom/index.min.js 4.66 kB
build/edit-post/classic-rtl.css 578 B
build/edit-post/classic.css 580 B
build/edit-post/index.min.js 12.7 kB
build/edit-post/style-rtl.css 2.31 kB
build/edit-post/style.css 2.31 kB
build/edit-site/index.min.js 217 kB
build/edit-site/posts-rtl.css 7.3 kB
build/edit-site/posts.css 7.3 kB
build/edit-site/style-rtl.css 12.6 kB
build/edit-site/style.css 12.6 kB
build/edit-widgets/index.min.js 17.7 kB
build/edit-widgets/style-rtl.css 4.2 kB
build/edit-widgets/style.css 4.2 kB
build/editor/index.min.js 102 kB
build/editor/style-rtl.css 9.28 kB
build/editor/style.css 9.29 kB
build/element/index.min.js 4.83 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 8.11 kB
build/format-library/style-rtl.css 476 B
build/format-library/style.css 476 B
build/hooks/index.min.js 1.54 kB
build/html-entities/index.min.js 445 B
build/i18n/index.min.js 3.58 kB
build/is-shallow-equal/index.min.js 526 B
build/keyboard-shortcuts/index.min.js 1.31 kB
build/keycodes/index.min.js 1.46 kB
build/list-reusable-blocks/index.min.js 2.18 kB
build/list-reusable-blocks/style-rtl.css 846 B
build/list-reusable-blocks/style.css 846 B
build/media-utils/index.min.js 3.2 kB
build/notices/index.min.js 946 B
build/nux/index.min.js 1.61 kB
build/nux/style-rtl.css 749 B
build/nux/style.css 745 B
build/patterns/index.min.js 7.34 kB
build/patterns/style-rtl.css 687 B
build/patterns/style.css 685 B
build/plugins/index.min.js 1.81 kB
build/preferences-persistence/index.min.js 2.06 kB
build/preferences/index.min.js 2.9 kB
build/preferences/style-rtl.css 554 B
build/preferences/style.css 554 B
build/primitives/index.min.js 829 B
build/priority-queue/index.min.js 1.54 kB
build/private-apis/index.min.js 1.01 kB
build/react-i18n/index.min.js 630 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 6.76 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.55 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.1 kB
build/router/index.min.js 1.96 kB
build/server-side-render/index.min.js 1.94 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 2.04 kB
build/token-list/index.min.js 581 B
build/url/index.min.js 3.9 kB
build/vendors/react-dom.min.js 41.7 kB
build/vendors/react-jsx-runtime.min.js 560 B
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 965 B
build/warning/index.min.js 250 B
build/widgets/index.min.js 7.2 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.03 kB

compressed-size-action

Copy link
Contributor

@joshuatf joshuatf left a comment

Choose a reason for hiding this comment

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

Fantastic work, @senadir! Love where this is going. It might be easier to review if we broke this PR into smaller chunks, but I left some high level thoughts throughout the PR.

I'm still torn between handling this at package level (by introducing a hook that allows providing dependencies) or leaving it up for implementors.

I think leaving this up to consumers sounds like a good strategy. In React, it's simple enough to throw the parser into a useEffect. Other consumers may require different implementations, so leaving this package simple works well IMO.

I don't have strong feelings about this, but I consider writing JSON objects with the same keys a bit too verbose for this goal, given how well defined the array shape is.

I agree. In an ideal world, we could keep everything in JSON Schema, but this feels overkill for this package and we can leave that responsibility to the validation package.

];
```

4. Nested arrays
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Thanks for considering these cases.

packages/rule-parser/README.md Outdated Show resolved Hide resolved
[
'ALL',
[
[ 'user.id', 'in', [ 1, 2, 3 ] ],
Copy link
Contributor

Choose a reason for hiding this comment

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

The dot notation makes me think we'll be able to access nested properties. For example, user.id would access the property here:

{
    "user": {
        "id": 5
    }
}

But looking at the sample data below, it looks like that might not be the case. Do we want to support dot notation or will consumers be responsible for flattening all their data?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The very initial implementation we had at Checkout operated that way, this added the need to parse things. I think we might want to provide even greater flexibility to consumers, so instead of passing an object of key value, they can also pass an object of keys and functions/loaders, or receive a single loader function and they decide what to do with it.

For now we can probably settle on supporting dot notation out of the box?

);
} );

it( 'should parse with newly introduced evaluator', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also have negative test cases around the newly introduced evaluator?

@@ -0,0 +1,155 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add some rules around thrown errors from data sources where inappropriate types are passed?

return source >= min && source <= max;
} );

registry.alias( '<>', 'between' );
Copy link
Contributor

Choose a reason for hiding this comment

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

Love the alias option! ❤️

source: Source,
target: Target,
rule: Rule,
strict: boolean = true
Copy link
Contributor

Choose a reason for hiding this comment

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

The name strict is throwing me a little bit. Maybe exclusive or inclusive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point!

@youknowriad youknowriad requested review from a team, getdave and cbravobernal and removed request for a team September 10, 2024 16:45
export type Value = string | number | boolean;
export type Source = Value | Value[];
export type Target = Value | Value[];
export type Operator = string;
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to type it as constants so that developers can get autocompletion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not I understand this, can you provide an example of what this should change to?

Copy link
Member

Choose a reason for hiding this comment

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

I believe the question is that rather than string can this be typed as a union of string literals: 'in' | '!in' | 'is' | ….

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yeah! It was originally like that, the issue I run into is that once you have an extensible registry that allows additional operators, the type would fail.

I suppose we can add a list of possible operators to the parser< T > call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suppose the type can be

Suggested change
export type Operator = string;
export type Operator =
| 'is'
| '='
| 'not is'
| '!='
| 'contains'
| 'not contains'
| '!contains'
| 'in'
| 'not in'
| '!in'
| 'greater than'
| '>'
| 'less than'
| '<'
| 'gte'
| '>='
| 'lte'
| '<='
| string;
@talldan
Copy link
Contributor

talldan commented Sep 11, 2024

This looks a bit like a lisp variant, I could imagine the syntax being like written like this in a lisp language:

(every (list
    (contains post-categories "tutorials")
    (is user-role 'editor')
))

note: I haven't written lisp in ages, so please don't criticize this code snippet too much!

I haven't really thought too deeply about the purpose of this, but focusing on the syntax and borrowing from Lisp, a suggestion would moving the function/operator to be a prefix so that there's plenty of future flexibility.

@ntsekouras
Copy link
Contributor

Thanks for the PR!

The main usage for this package would be to solve a couple of problems:

Giving developers a unified API to control visibility in the DataForms (cc @joshuatf).
Provide an API to bring in the main functionality of https://blockvisibilitywp.com/ into Gutenberg (cc @ndiego ).
Give plugin authors a unified API and structure to implement rules for their developers, the main case I'm building this for is to have developers show/hide fields in Checkout depending on customer context as well as accept values depending on that.

Can you provide some more details how this package will help core? Will existing code be simplified? Do we have many needs for such code?

@senadir
Copy link
Contributor Author

senadir commented Sep 11, 2024

Hey @ntsekouras!

Can you provide some more details how this package will help core? Will existing code be simplified? Do we have many needs for such code?

There's no current code that we can rewrite to benefit from this, we're kinda jumping the gun here and working on parallel, the team working on DataForms will most definitely need that, and we (downstream consumers) also need that, so there's benefit from standardizing on this early on.

When I talked with Matias about this, he did express interest in bringing Blocks Visibility functionality eventually into core.

so TL;DR: there's no direct usage, this package will sit, and be actually used outside Gutenberg first and inside Gutenberg in the coming months.

@senadir
Copy link
Contributor Author

senadir commented Sep 11, 2024

Thanks for the feedback @talldan! would this mean switching to [ 'contains', 'post.categories', '5' ] In the hope that we can add a forth or fifth argument?

@talldan
Copy link
Contributor

talldan commented Sep 11, 2024

Thanks for the feedback @talldan! would this mean switching to [ 'contains', 'post.categories', '5' ] In the hope that we can add a forth or fifth argument?

Yep 👍 . I saw the argument for keeping it at 3 items in the PR description, but I still think it might be good to leave it open to more arguments as a future possibility, or even support singular arguments (e.g. [ 'isTruthy', 'post.author' ]).

@ntsekouras
Copy link
Contributor

so TL;DR: there's no direct usage, this package will sit, and be actually used outside Gutenberg first and inside Gutenberg in the coming months.

In that case, why not create a completely different npm package (outside of WP), start using it and test it as 3rd party consumers. If everything works well, we can port to WP core to have the benefits of something used in core and needed from others. If it doesn't work well, you will be able to adjust with any breaking changes you need and then port.

Introducing packages and public APIs in WP are a big burden, as we should always be backwards compatible. We should be very careful and intentional these matters. --cc @WordPress/gutenberg-core

@senadir
Copy link
Contributor Author

senadir commented Sep 11, 2024

Thanks for the feedback Nik! I will explore that.

We do have validated need for this in Gutenberg, though it's a bit early. I totally understand the concern of expanding the public area, which is why I wanted to only expose this via npm first, so that we can iterate on any breaking changes in a single place first. Would that still have the same risk?

@mirka
Copy link
Member

mirka commented Sep 12, 2024

This package is only meant to be used by implementors, not direct developers

I was a bit confused by this verbiage. Does "implementors" here mean something like "platform developers", and "direct developer" a "third-party developer" (an "extender" in WP terminology)?

@senadir
Copy link
Contributor Author

senadir commented Sep 12, 2024

I was a bit confused by this verbiage. Does "implementors" here mean something like "platform developers", and "direct developer" a "third-party developer" (an "extender" in WP terminology)? — @mirka

Hey Lena! I can probably do a better job of explaining this, but your explaining is exactly correct. This package is simply an underlying tool (this is a similar one I discovered yesterday).

An Implementor would be Gutenberg or WooCommerce, they would be responsible for collecting rules from their end-developers/extenders, they would provide the context (cart.cartTotals = 150), they would execute the parser, and they would take that value and run with it.

I will adopt extenders, but looking for something to replace Implementors.

@oandregal
Copy link
Member

Hey, interesting work here!

Why do you want this package to live in the Gutenberg repository? It does a lot of things up-front without solving (even a minimal) use case in core. We haven't added unused packages before, and I am reluctant to start doing it. It's a difficult line to walk. How do we decide what's going to be used and what not based on any plugin's roadmap?

I'd be happy to have this here if it's applied to a concrete use case in core. If what you envision is not ready yet in core, perhaps it can live elsewhere (@woo/rule-parser?) and core can use it when/if time comes — like any other npm package we consume. Or then we switch it to @wordpress/rule-parser if it makes more sense.

@joshuatf
Copy link
Contributor

I think there are a couple of questions we need to answer here:

1. Does core have a need for this?

I'm certain that downstream consumers of DataForms will need a way to conditionally hide or show fields in an extensible way, but I'm not certain whether or not this immediate need exists in core yet.

@youknowriad @oandregal Will block settings be replaced by DataForms? If so, image block settings might be an example of an area that could benefit from this type of parser. (See "Scale" is shown/hidden depending on "Aspect Ratio" selection).

Screenshot 2024-09-11 at 10 32 09 PM

2. Is there enough crossover with a validation library that would create redundancies across packages?

This question was brought up by @dmsnell and I think is worth revisiting. Core has a more immediate need for a data validation library and JSON Schema seems like a viable approach to a validator package. Below are the above examples translated to JSON Schema.

// const rule = [
//     [ 'user.role', 'is', 'editor' ]
// ];
{
    "$schema": "https://json-schema.org/draft/2019-09/schema",
    "type": "object",
    "properties": {
        "role": {
            "const": "editor"
        }
    }
}

// const rules = [
//     [ 'user.role', 'is', 'editor' ],
//     [ 'post.categories', 'contain', 'tutorials' ]
// ];
{
    "$schema": "https://json-schema.org/draft/2019-09/schema",
    "type": "object",
    "properties": {
        "role": {
            "const": "editor"
        },
        "categories": {
            "type": "array",
            "contains": {
                "const": "tutorials"
            }
        }
    }
}


// const rules = [
//     'ANY',
//     [
//         [ 'user.role', 'is', 'editor' ],
//         [ 'post.categories', 'contain', 'tutorials' ],
//         [ 
//             'ALL',
//             [
//                 [ 'user.id', 'in', [ 1, 2, 3 ] ],
//                 [ 'post.blocks', 'not contain', 'core/embed' ]
//             ]
//         ]
//     ]
// ];
{
    "$schema": "https://json-schema.org/draft/2019-09/schema",
    "type": "object",
    "anyOf": [
        { 
            "properties": {
                "role": {
                    "const": "editor"
                },
            }
        },
        {
            "properties": {
                "categories": {
                    "type": "array",
                    "contains": {
                        "const": "tutorials"
                    }
                }
            }
        },
        {
            "properties": {
                "id": {
                    "type": "number",
                    "enum": [ 1, 2, 3 ]
                },
                "blocks": {
                    "not": {
                        "contains": {
                            "const": "core/embed"
                        }
                    }
                }
            }
        }
    ]
}

This is undoubtedly more verbose, but much more declarative and JSON Schema is well documented with a number of pre-existing tools that we can use.

If we opt to use JSON Schema, we can combine the efforts here and validation into a single package and focus on creating a package that core has a more immediate use for.

@senadir
Copy link
Contributor Author

senadir commented Sep 13, 2024

The goal of pushing right now is to agree on a shape that we can confidently adopt in WooCommerce, and have the assurance that we're not introducing yet another solution for the same problem. It's a problem we know Gutenberg will need solving soon, just not yet. Given this is going to be a public API for us, it's hard to ask developer to use this rule shape, then several months later, Gutenberg ship the same thing, but using a different schema, and we now have 2 ways to do things, and have diverged from core.

@oandregal @ntsekouras Ignoring the code (happy to close this one right now, as for me, this is a means to an end): is it possible to adopt a style guide for how rules should be written? Without shipping code for it? I understand this is a hard question to answer without actually putting it for use? But if we can do, and you point me to where to go, I would be happy to move away for this, get a proposal in place, and adopt it in WooCommerce.

@senadir
Copy link
Contributor Author

senadir commented Sep 23, 2024

Thank you everyone for the feedback, I highly appreciate the candid responses here.

I'm going to close this package and follow up with a new PR that introduces validation and conditional visibility, based on JSONSchema. We will have direct use cases for this inside the DataViews/DataForms work, and likely to ship it there first before breaking out to its own package. Let me know if you want to be involved in reviewing/approving that PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] New API New API to be used by plugin developers or package users.
8 participants