--- AI TRACKER METADATA ---
Update Summary: Simple approach to bringing advanced metadata into Canvas AI
Check-in Date: MM/DD/YYYY (US format) [When we should see progress/get an update]
Due Date: MM/DD/YYYY (US format) [When the issue should be fully completed]
Blocked by: [#XXXXXX] (New issues on new lines)
Additional Collaborators: @username1, @username2
AI Tracker found here: https://www.drupalstarforge.ai/
--- END METADATA ---

Overview

CanvasAiPageBuilderHelper::getAllComponentsKeyedBySource() can be called multiple times when the Canvas AI Component Description Settings Form is viewed/saved or when the get_component_context tool is used by the Canvas AI Page Builder agent. This method fetches all available components from canvas.api.config.list for further processing.

In the initial implementation, the controller method ApiConfigControllers::list was directly invoked from getAllComponentsKeyedBySource() to fetch the available components. This was later replaced with a sub-request in #3545477: Use subrequests when getting component list in `::getAllComponentsKeyedBySource()` for better cacheability.

However, this sub-request interferes with the Canvas AI Component Description Settings Form, causing the following issues:

  • The form title is not getting rendered.
  • The user gets redirected to the canvas.api.config.list route once the form is submitted.

Debugging showed that Form API considers the sub-request route as the redirect path when submitting the form. One way to fix this issue was to set an explicit redirect to the form route ($form_state->setRedirect('canvas_ai.component_description_settings');) or disable the redirect completely ($form_state->disableRedirect()). While this works, it has the following disadvantages:

  • The title of the form still does not get rendered.
  • The status message (“The configuration has been saved”) does not get displayed after the form is submitted, even if the message is explicitly added in the submit handler.

Proposed resolution

I think the sub-request can be eliminated altogether, and we can switch back to calling the controller method directly. Then we can cache the results using the component:list cache tag so that it gets invalidated every time components are updated.

User interface changes

Issue fork canvas-3558241

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

akhil babu created an issue. See original summary.

akhil babu’s picture

buildForm() and SubmitForm() methods in the form calls Drupal\canvas_ai\CanvasAiPageBuilderHelper::getAllComponentsKeyedBySource to get the active components. Under the hood getAllComponentsKeyedBySource triggers a sub request to the 'canvas.api.config.list' route get fetch available components from the API. For some reason, the Form api considers this request as the current request and redirects users once the form is sumitted

akhil babu’s picture

Status: Active » Needs review
akhil babu’s picture

Assigned: akhil babu » Unassigned
narendrar’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

The changes work as expected, but status messages are not being displayed on the page. This could be due to the use of a sub-request.
Also, it would be great to have some tests to validate this form.

akhil babu’s picture

Status: Needs work » Needs review

Thanks for reviewing this @narendrar. While reworking this, I feel that the sub-requests added in #3545477: Use subrequests when getting component list in `::getAllComponentsKeyedBySource()` might not be needed. This sub-request gets fired every time the form is loaded and/or submitted (and also when get_component_context gets invoked (Drupal\canvas_ai\CanvasAiPageBuilderHelper::getComponentContextForAi)). Even if we cache the results, the sub-request will get fired at least once when the user loads the form for the first time. Due to this, the form title won't get displayed as it interferes with the form request.

I feel the better approach is to bring back the original logic of invoking the controller method here, do the necessary calculations, and then cache the result. This eliminates the sub-request and thus fixes the issue with the form.

Regarding the tests, I think it makes more sense to add a test for the get_component_context tool to ensure that
1. If user doesnt use the form, this tool returns the components with their default descriptions
2. If user uses the form to override component descriptions, the tool returns those.

That can be it's own separate issue. What do you think?

tedbow’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

It would be good to get simple description of the actual problem that is trying to be solve otherwise it is hard to tell if it fixes anything.

For example

Overview

When the user is at a/b/c path and submits the form they are then redirected to "/some/other/path" under [some circumstances, or always?]

This is bad because [some reason]

The user should be redirected to /the/correct/path and see [something]

Proposed resolution

redirect the user to [somewhere]

tedbow’s picture

re

Regarding the tests, I think it makes more sense to add a test for the get_component_context tool to ensure that
1. If user doesnt use the form, this tool returns the components with their default descriptions
2. If user uses the form to override component descriptions, the tool returns those.

That can be it's own separate issue. What do you think?

I think this issue needs tests. If there had already been a simple test that you could submit the form and then would be redirected to the correct place before #3545477: Use subrequests when getting component list in `::getAllComponentsKeyedBySource()` then that test would have failed when they made that change and we wouldn't have committed until it was fixed in the first place. So regardless of the solution lets get a functional test that proves this form can be submitted and you will be redirected correctly.

Due to this, the form title won't get displayed as it interferes with the form request.

This sounds like something that would be good to cover in the test

tedbow’s picture

In the summary it would also be good to get the context of why #3545477: Use subrequests when getting component list in `::getAllComponentsKeyedBySource()` was done in the first place. It seems there were performance concerns and the were manually tested by narendrar. Has this been re-tested with the new solution? to make sure there is a not a regression.

I have not review the actual solution yet just trying to get more context

akhil babu’s picture

Assigned: Unassigned » akhil babu
akhil babu’s picture

Issue summary: View changes
akhil babu’s picture

I have added the following tests
1. CanvasAiComponentDescriptionSettingsFormTest.php – verifies that the form title is rendered correctly and that form validation and submission behave as expected.
2. CanvasAiPageBuilderHelperTest.php – checks whether the component list is properly cached and ensures that the cache is invalidated when components are updated.

While creating the CanvasAiComponentDescriptionSettingsFormTest, I encountered the following error when submitting the form in the test:

Schema errors for canvas_ai.component_description.settings with the following errors: canvas_ai.component_description.settings:component_context.block missing schema in Drupal\Core\Config\Development\ConfigSchemaChecker->onConfigSave() (line 98 of core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php).

This error does not occur when manually saving the form from the UI.
To work around this in the test, I had to add: protected $strictConfigSchema = FALSE; in the test. Will create a followup issue to check this further.

We also need additional tests to verify:

  • When a component/prop/slot description is overridden using the form, the tool should return the overridden description.
  • When components from a particular source (e.g. Blocks) are disabled using the form, the get_component_context tool should return only the components from the enabled sources.

These tests can be incorporated along with #3545816: Simple approach to bringing advanced metadata into Canvas AI, as that issue modifies the output of the get_component_context tool.

Edit: Created #3560231: Canvas AI: Define proper config schema for CanvasAiComponentDescriptionSettingsForm

akhil babu’s picture

Assigned: akhil babu » Unassigned
Status: Needs work » Needs review
akhil babu’s picture

Assigned: Unassigned » akhil babu
Status: Needs review » Needs work

Updating the tests

akhil babu’s picture

Assigned: akhil babu » Unassigned
Status: Needs work » Needs review

Ready again

akhil babu’s picture

Issue summary: View changes
akhil babu’s picture

The schema related error mentioned in #13 have been resolved now.

rakhimandhania’s picture

Issue tags: +AI Innovation
rakhimandhania’s picture

scott falconer made their first commit to this issue’s fork.

akhil babu’s picture

Status: Needs review » Needs work

Thanks, Scott. Some changes I had earlier in MR 331 are no longer part of the MR now. I am not sure how they got reverted.

The missing changes are:
1. A functional test for the form: https://git.drupalcode.org/project/canvas/-/blob/2d7eda23c9f693af965499e...
2. Some additional changes needed in CanvasAiPageBuilderHelper::processSdc to fix certain bugs identified while running the above test

Since we now have two separate MRs, I am not sure which one these changes should be added to.

scott falconer’s picture

Thanks, Akhil! I definitely agree we should consolidate everything into your MR !331 and keep that as the canonical merge request.

Regarding the changes: My MR (!524) was created as a fresh branch based on the current code state, so it didn't include those specific older commits from your history. I suspect they may have dropped off the MR !331 branch history during a previous rebase or update. I certainly didn't intend to remove them.

Since GitLab allows collaboration on your issue fork, I am happy to fix this up so we have one complete, passing MR.

I think the the cleanest way to fix this is for me to push the work directly to your branch. If you are okay with that, I will:

- Restore your missing work: I'll cherry-pick/re-add the functional test commit you linked (2d7eda23) and the processSdc fixes.
- Apply my fixes: I'll add the schema updates and the cache correctness fixes from my MR as new commits on top.
- Ensure Green Tests: I'll make sure the pipeline passes with both your tests and the regression tests I added.

Do I have your permission to push these commits directly to your MR !331 branch?

If you prefer to keep stricter control, I can instead provide a patch or a clean list of commits for you to cherry-pick yourself. Just let me know what works best for you.

akhil babu’s picture

Thanks, Scott. That sounds good to me.

The functional test I added already verifies multiple aspects of the form, including:

  • The form title is displayed correctly
  • Validations are triggered as expected
  • Default values appear correctly after form submission
  • The user remains on the form after submit

So i think the test you added (CanvasAiFormRedirectTest) may not be required.

I had also added a kernel test to verify that component data is properly cached:
canvas_ai/tests/src/Kernel/CanvasAiPageBuilderHelperTest.php::testGetAllComponentsKeyedBySourceCaching()
Looks like something went wrong during a rebase on my side, so feel free to go ahead and push the changes directly to my MR. If anything here is unclear or confusing, please let me know. I am also happy to handle it from my side if that is easier.

TL;DR – changes to be included:

scott falconer’s picture

Thanks Akhil, I restored the missing pieces you linked and pushed them onto your MR !331 branch.

MR !331 now includes commit 66bb3c09 (“Fix #3558241: restore tests + schema + SDC prop handling”), which brings back:

  • Functional form test: CanvasAiComponentDescriptionSettingsFormTest
  • Kernel caching test: CanvasAiPageBuilderHelperTest::testGetAllComponentsKeyedBySourceCaching()
  • processSdc() fixes needed for the test
  • The strict config schema fix for component_context

I did not keep my separate redirect-only functional test, since your form test already covers the redirect/title/message/validation behavior.

Local testing (DDEV) on my side:

ddev phpcs … (pass)
ddev phpunit …CanvasAiComponentDescriptionSettingsFormTest.php (pass; deprecations only)
ddev phpunit …CanvasAiPageBuilderHelperTest.php (pass; deprecations only)

CI pipeline is running; note that lint (php) currently fails due to PHPStan errors in src/* (not canvas_ai). Happy to help investigate that separately if needed.

akhil babu’s picture

Status: Needs work » Needs review

I incorporated the config schema and caching-related changes from MR !524, since those caching updates were more robust than my original approach.
While testing everything locally, I found an issue related to caching, which I've commented on in MR !524, along with the fix.

MR !331 now contains all the required changes. Could you please review it so we can close !524?

akhil babu’s picture

Status: Needs review » Needs work

Ahh… previously, the test CanvasAiPageBuilderHelperTest::testGetAllComponentsKeyedBySourceCaching used a static cache ID to verify that the cache was invalidated correctly when a component was disabled. However, we no longer have a static cache ID, so the test is now failing.

Can we make the methods getComponentListCacheability() and buildCacheId() public?

Also, since we are already caching the results, do we still need the in memory caching via CanvasAiPageBuilderHelper::$fetchedComponents?

scott falconer’s picture

Good catch, since the cache ID is now varied by cache contexts, the kernel test can’t use a static cache key anymore. I updated the kernel test to compute the same context-varied cache ID (cache_contexts_manager + list contexts + user.permissions), so we don’t need to make helper internals public.

as to $fetchedComponents, it’s and optional per-request optimization since this helper is called multiple times per request but happy to drop it if you prefer.

akhil babu’s picture

Thanks for updating the test. I think we should consider removing it, as it exposes an edge case where the cache is invalidated but a stale component context is still shared with the user.

I was able to confirm this behavior with the following steps.

In CanvasAiPageBuilderHelper::getAllComponentsKeyedBySource, I added a log statement'echo "Using memory cached components" inside

    if ($this->fetchedComponents !== NULL) {
      echo "Using memory cached components\n";
      return $this->fetchedComponents;
    }

Then Called $this->canvasAiPageBuilderHelper->getAllComponentsKeyedBySource(); again in CanvasAiPageBuilderHelperTest::testGetAllComponentsKeyedBySourceCaching() just above $this->assertFalse($cached2, 'Cache is invalidated after component disable');

Since the test disables a component, calling getAllComponentsKeyedBySource() again should fetch the components from the source. However, I still saw the log message "Using memory cached components". This indicates that even though the cache was invalidated, the in-memory cache remained in effect, resulting in stale data being reused.

scott falconer’s picture

Status: Needs work » Needs review

Agreed. fetchedComponents removed in last MR.

akhil babu’s picture

Status: Needs review » Reviewed & tested by the community

Ran the test again in local and it works as expected. Thanks.
BTW, I am not sure if I am the right person to move this to RTBC, since I have also partially worked on it 😅.

mglaman made their first commit to this issue’s fork.

mglaman’s picture

Status: Reviewed & tested by the community » Needs work

Left a handful of comments. I'm also not sure which MR is the correct one 331 or 524? Or has 524 been incorporated into 331?

scott falconer’s picture

331 is the correct one. I've closed 524.

scott falconer’s picture

updated !331

- Added strict config schema validation CanvasAiComponentDescriptionSettingsFormTest.php:34
- Refined subrequest overhead comment to describe route-context side effects
- Applied cacheability dependency directly on the response object
- Replaced string FQCN with class constant for Attribute
- Replaced __toString() with explicit cast.
- tabs vs spaces looks like it was resolved earlier.

- For use a static property to avoid multiple cache hits we discussed request-local memoization above but caused stale data after cache invalidation so removed.

- For Use VariationCache bin I agree but this seems like a pretty big refactor, happy to add it here though if it makes sense.

scott falconer’s picture

Status: Needs work » Needs review
mglaman’s picture

Status: Needs review » Needs work

modules/canvas_ai/config/schema/canvas_ai.schema.yml conflcts, needs a manual rebase.

rakhimandhania’s picture

Issue tags: +AI Page Generation
jibran’s picture

Assigned: Unassigned » jibran

Working on a rebase.

jibran changed the visibility of the branch 3558241-canvas-ai-canvasaicomponentdescriptionsettingsform to hidden.

jibran changed the visibility of the branch 3558241-canvas-ai-canvasaicomponentdescriptionsettingsform to active.

akhil babu’s picture

#3573571: Use VariationCache for getAllComponentsKeyedBySource() cache context handling would fix this issue in a much better way. Sorry, I added it as a comment in that issue not this one.
@jibran, it would be helpful if you could review #3573571: Use VariationCache for getAllComponentsKeyedBySource() cache context handling instead.

jibran’s picture

I think we need to block #3573571: Use VariationCache for getAllComponentsKeyedBySource() cache context handling on this. We need to fix the config schema definition, the post-update hook, and the helper class before implementing the cache fix.

jibran’s picture

Assigned: jibran » Unassigned
Status: Needs work » Closed (duplicate)

@akhil babu and @scott falconer have made significant progress on #3573571: Use VariationCache for getAllComponentsKeyedBySource() cache context handling, including the upgrade path, so we can close this as a duplicate now.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

jibran’s picture

@rakhimandhania can we please move the spirint tag over in #3573571: Use VariationCache for getAllComponentsKeyedBySource() cache context handling