Determine which core config entity methods should be config actions

Created on 11 August 2022, almost 2 years ago
Updated 17 June 2024, about 14 hours ago

Problem/Motivation

In #3284025: Add configuration actions API β†’ we added the ability to add an attribute to config entity methods to make them available to the config actions APIs and, thereby, recipes. We added the attribute to \Drupal\user\Entity\Role::grantPermission() like so:

  /**
   * {@inheritdoc}
   */
  #[ActionMethod(adminLabel: new TranslatableMarkup('Add permission to role'))]
  public function grantPermission($permission) {

The question is what other core provided config entity methods should be available to actions?

Proposed resolution

  1. Determine the methods to make into actions
  2. Make it so

Remaining tasks

User interface changes

API changes

Data model changes

πŸ“Œ Task
Status

RTBC

Version

11.0 πŸ”₯

Component
Configuration entityΒ  β†’

Last updated about 8 hours ago

Created by

πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    What are the "core provided config entity methods"?

    Would someone be able to list, or link to a list?

  • πŸ‡¨πŸ‡¦Canada nedjo

    @thejimbirch

    Here are some quick pointers that I hope may be enough to get you started.

    Config entity types are defined via the ConfigEntityType annotation, see the relevant documentation page β†’ . To list core-provided config entity types, search api.drupal.org for ConfigEntityType and click on the link for the annotation class with the description "Defines a config entity type annotation object." On the resulting page, click to expand "33 classes are annotated with ConfigEntityType" and then click "See full list". Then click on each of the listed types: Action, Block, and so on--though we can presumably skip anything with "Test" in its name.

    How do we start to decide which methods should be config actions? Here are some thoughts and suggestions.

    For starters, it has to be something that modifies the config entity. Often, that's reflected in a verb that starts the method name. The most common such verb is "set", but there are others.

    Once you bring up the class for a config entity type - let's take Block as an example - scroll down to the "Members" section. For "Name does not contain", enter "Test" and for "Type" enter "Function" and click "Filter". In the resulting list, you can ignore any that say "protected" under "Modifiers", and also any function starting with "get" ("getPlugin" and so on), since by definition those methods won't alter anything. Conversely, anything starting with "set" is probably a strong candidate for a config action. For Block, that starts with "Block::setRegion".

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    In doing some research, I discovered another thing we'd want to grant in some situations - the ability to change the configuration options of a plugin that powers a config entity.

    A good example here is adding an additional entity type or bundle to a Content Moderation workflow. In this case, the workflow itself is not really the thing to update - we need to get the workflow's type plugin, and tell it to make the change as needed. How could this be represented in config actions?

    Media types are another example of a plugin-backed config entity that would need similar handling.

  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Another example: changing filter plugin settings for an enabled filter plugin in a text format.

    And AFAIK the most complex example possible: changing the CKEditor 5 plugin settings for a text editor that happens to be using the CKEditor 5 plugin β€” this is AFAIK the only config entity with multiple layers of indirection.

    If we can make config actions work there, then we can make them work everywhere AFAICT.

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    From #3417835-14: Convert the Standard install profile into a set of recipes β†’ - it would make sense to have the setter methods of \Drupal\contact\ContactFormInterface be config actions:

    • \Drupal\contact\ContactFormInterface::setMessage()
    • \Drupal\contact\ContactFormInterface::setRecipients() -- there should also be an addRecipient action (and a pluralized version of that), which adds a single recipient to an existing list.
    • \Drupal\contact\ContactFormInterface::setRedirectPath()
    • \Drupal\contact\ContactFormInterface::setReply()
    • \Drupal\contact\ContactFormInterface::setWeight()
  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    #7: excellent info β€” that's exactly the kind of real-world research we need to move this forward IMO 🀩

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts
  • Merge request !115Add a lot of action methods β†’ (Closed) created by phenaproxima
  • Pipeline finished with Success
    2 months ago
    Total: 522s
    #149676
  • Status changed to Needs review 2 months ago
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    OK, I ended up taking a whirlwind tour through core and marking a lot of entity methods as action methods.

    Curiously, most of them don't make sense in pluralized form (things like setting the weight, or changing the name, of an entity -- you only do that once in any given receipe, they're not really methods that take multiple values). So these all got pluralize: FALSE in their annotation.

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts
  • Merge request !7940Resolve #3303127 "Add config actions" β†’ (Open) created by phenaproxima
  • Pipeline finished with Failed
    about 1 month ago
    Total: 175s
    #166315
  • Pipeline finished with Failed
    about 1 month ago
    Total: 574s
    #166320
  • Pipeline finished with Success
    about 1 month ago
    Total: 699s
    #166327
  • Pipeline finished with Canceled
    about 1 month ago
    Total: 164s
    #166341
  • Pipeline finished with Success
    about 1 month ago
    Total: 589s
    #166346
  • Pipeline finished with Success
    about 1 month ago
    Total: 577s
    #166353
  • Pipeline finished with Success
    about 1 month ago
    Total: 607s
    #166422
  • Pipeline finished with Canceled
    about 1 month ago
    Total: 302s
    #166436
  • Pipeline finished with Canceled
    about 1 month ago
    Total: 445s
    #166441
  • Pipeline finished with Success
    about 1 month ago
    Total: 576s
    #166452
  • Pipeline finished with Canceled
    about 1 month ago
    Total: 221s
    #166515
  • Pipeline finished with Success
    about 1 month ago
    Total: 577s
    #166518
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Okay, I think this is ready for a look.

    I took a pretty broad approach to exposing config entity methods as actions, and added test coverage for every one of them. Some methods I omitted on purpose because they couldn't work from an API perspective, despite being useful (like \Drupal\image\Entity\ImageStyle::deleteImageEffect); others I skipped because their usefulness is dubious (like \Drupal\search\Entity\SearchPage::setPlugin).

    Would be curious what people think.

  • πŸ‡¨πŸ‡¦Canada Laura Johnson Toronto

    This looks good to me, except that I find it a little odd that in some cases we label it in agreement with the method verb:
    setRequired() label = 'Set whether field is required'

    ...but other times instead of 'set' it's 'change'
    setSettings() label = 'Change field settings'

    I think I would always go with 'Set' (or whatever the verb of the method is).

    I'll test all of these in a recipe at contrib day tomorrow.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    Am I thinking of this correctly? Based on these changes we can update following:

    ## Field instances:

    config:
      action:
        field.field.*.*.ymal
          # Change field label
          setLabel: 'words'
          # Change field description
          setDescription: 'words'
          # Set whether field is translatable
          setTranslatable: true
          # Change field settings
          setSettings:
            on_label: 'Yes'
            off_label: 'No'
          # Set whether field is required
          setRequired: true
    

    ### And we can't update:

    uuid: ...
    langcode: en
    dependencies: ...
    id: entity.bundle.field_bar
    field_name: field_bar
    entity_type: bar
    bundle: bar
    default_value: {  }
    default_value_callback: ''
    field_type: entity_reference
    
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Correct.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    ## Block

    config:
      action:
        block.block.*.yml
          # Set block region
          setRegion: content
          # Set block weight
          setWeight: 10

    ### And we can't update:

    uuid: ...
    langcode: en
    status: true
    dependencies: ...
    id: unique_id
    theme: themw_name
    provider: null
    plugin: system_main_block
    settings: ...
    visibility: ...
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    All correct.

    It's pretty much a subjective judgment call, but I only exposed methods that I think are likely to be useful, but not cause additional complications. Complicated things need their own issues and/or dedicated config actions, so we can think through and address their implications.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    That makes perfect sense. Thank you for your patience as I work through this.

    It is helping my brain understand better making a list of what we can and cannot do per config type. I feel like it will make a good documentation resource also.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    ## Contact form

    config:
      action:
        contact.form.*.yml
          # Set contact form message
          setMessage: 'words'
          # Set recipients
          setRecipients: 'admin@example.com'
          # Set redirect path
          setRedirectPath: '/path/to/page'
          # Set auto-reply message
          setReply: 'admin@example.com'
          # Set weight
          setWeight: 10
    

    ### And we can't update:

    langcode: en
    status: true
    dependencies: {  }
    id: personal
    label: 'Personal contact form'
    
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    So about that...

    It's not really true that you can't update label...it's just that there's no dedicated method for it on the ContactForm class. But this MR adds generalized access to ConfigEntityBase::set(), so you could do this:

    contact.form.feedback:
      set:
        key: label
        value: Personal contact form
    

    ...but that's sort of a lower-level action, so maybe a bit of an advanced use case.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    That's great! Updating comment #22, thanks!

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    Updated the labels based on the comment in #16 and phenaproxima's thumbs up. I can't resolve the comments, but they should be closed.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    I believe this is everything we can do with these additions.

    ## Block

    config:
      action:
        block.block.*.yml
          # Set entity status
          setStatus: true
          # Set block region
          setRegion: content
          # Set block weight
          setWeight: 10

    ### And we can't update:

    uuid: ...
    langcode: en
    dependencies: ...
    id: unique_id
    theme: theme_name
    provider: null
    plugin: system_main_block
    settings: ...
    visibility: ...

    ## Contact form

    config:
      action:
        contact.form.*.yml
          # Set entity status
          setStatus: true
          # Set contact form message
          setMessage: 'words'
          # Set recipients
          setRecipients: 'admin@example.com'
          # Set redirect path
          setRedirectPath: '/path/to/page'
          # Set auto-reply message
          setReply: 'admin@example.com'
          # Set weight
          setWeight: 10
          # We can use the `set` action to update the label
          set:
            key: label
            value: Personal contact form
    

    ### And we can't update:

    langcode: en
    dependencies: {  }
    id: personal
    

    ## Field instances:

    config:
      action:
        field.field.*.*.yml
          # Change field label
          setLabel: 'words'
          # Change field description
          setDescription: 'words'
          # Set whether field is translatable
          setTranslatable: true
          # Change field settings
          setSettings:
            on_label: 'Yes'
            off_label: 'No'
          # Set whether field is required
          setRequired: true
    

    ### And we can't update:

    uuid: ...
    langcode: en
    dependencies: ...
    id: entity.bundle.field_bar
    field_name: field_bar
    entity_type: bar
    bundle: bar
    default_value: {  }
    default_value_callback: ''
    field_type: entity_reference
    

    ## Image

    config:
      action:
        image.style.*.yml
          # Use set to update name and label
          set:
            key: name
            value: foo
            key: label
            value: bar
          # Add an image effect
          addImageEffect:
            id: image_scale
            weight: 1
            data:
    	      width: 1300
    	      height: 1300
    	      upscale: false
    

    ### And we can't update:

    langcode: en
    dependencies:
    

    ## Language

    config:
      action:
        language.entity.*.yml
          # Set entity status
          setStatus: true
          # Set Language name
          setName: foo
          # Set weight
          setWeight: 1
          # Use set to update direction and locked
          set:
            key: direction
            value: rtl
            key: locked
            value: true
    

    ### And we can't update:

    langcode: en
    dependencies: {  }
    id: es
    direction: ltr
    locked: false
    

    ## Node

    config:
      actions:
        node.type.*.yml
          # We can use the `set` action to update the name and description
          set:
            key: name
            value: Article
            key: description
            value: 'Use <em>articles</em> for time-sensitive content like foo, bar'
          # Set entity status
          setStatus: true
          # Set revisions or not
          setNewRevision: false
          # Change the Preview mode
          setPreviewMode: 2
          # Change the Display Submitted.
          setDisplaySubmitted: false
    

    ### And we can't update:

    langcode: en
    type: article
    help: null
    

    ## Media

    config:
      actions:
        media.type.*.yml
          # We can use the `set` action to update things
          set:
            key: label
            value: New name
            # Queue thumbnail downloads
            key: queue_thumbnail_downloads
            value: false
          # Set entity status
          setStatus: true
          # Set Description
          setDescription: "words"
          # Set revisions or not
          setNewRevision: false
          # Set field mapping
          setFieldMap:
            name: name
    

    ### And we can't update:

    langcode: en
    dependencies: {  }
    id: image
    source: image
    source_configuration:
      source_field: field_media_image
    
  • Status changed to Needs work 23 days ago
  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    This MR is awesome. It will really help push config actions forward. I have some questions and comments.

    1. The tests are testing the additions here and helped me understand the options. However, they are far from complete. Could we update these test to cover all of the possible changes you can do per config type? Or would that be better suited for a new issue?

    2. If we can do things with just the set config action, do we need to have all the specificity?

    For example, on Contact form we can set the label name with:

          set:
            key: label
            value: Personal contact form
    

    Yet on Language we have setName: foo.

    And on Field instances we have setLabel: 'words'

    3. In the example above, we have setName and setLabel. Can we decide on one for consistency?

    4. I made some assumptions in the documentation I put in the comment above this that we could use set: on things. Could someone validate those will work?

  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    #27.3: I think the current MR is consistent with the naming of each config entity type. Sadly, not all config entity types are consistent.

    I don't think it makes sense to change that here β€” if you want that consistency, we should be enforcing it at the config entity type level instead, and provide update paths for those config entities. That'd be a separate issue IMHO.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    Thanks @Wim Leers. That makes sense.

    Also need to document:

    set:
    setMultiple:
    removeComponent:
    
  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    #27.2:

    If we can do things with just the set config action, do we need to have all the specificity?

    Yes, yes we do. set() is an extremely dumb method; it just updates a value on the object, regardless of whether it makes any sense as passed, or needs additional massaging or processing to be valid/useful. It's handy for filling in incomplete entity APIs, but it's fundamentally a shim. The more specific methods are the ones that should be used wherever possible.

    #27.3:

    In the example above, we have setName and setLabel. Can we decide on one for consistency?

    Not with the ActionMethod annotation, we can't. Using that annotation, the config action is named for the entity method. Renaming it would (I think) require a whole separate config action, or at least some kind of alter hook.

    #27.4:

    I made some assumptions in the documentation I put in the comment above this that we could use set: on things. Could someone validate those will work?

    Yes, we have set: and setMultiple:. They are both explicitly tested in EntityMethodConfigActionsTest::testSet(). However, in your comment, the syntax is wrong. To call set() a single time, it's like this:

          set:
            key: label
            value: New name
    

    To call it multiple times:

          setMultiple:
            -
              key: label
              value: New name
            -
              # Queue thumbnail downloads
              key: queue_thumbnail_downloads
              value: false
    
  • Status changed to RTBC about 14 hours ago
  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    Thanks for answering my questions! I updated my examples in #26 to use setMultiple.

    This is a great step forward for Actions. I am marking as RTBC.

Production build 0.69.0 2024